@travetto/di 5.0.0-rc.0 → 5.0.0-rc.10
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 +14 -13
- package/package.json +3 -3
- package/src/decorator.ts +13 -18
- package/src/error.ts +1 -1
- package/src/registry.ts +29 -46
- package/src/types.ts +1 -1
- package/support/dynamic.injection.ts +6 -9
- package/support/test/suite.ts +3 -3
- package/support/transformer.injectable.ts +11 -5
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ yarn add @travetto/di
|
|
|
16
16
|
[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.
|
|
17
17
|
|
|
18
18
|
## Declaration
|
|
19
|
-
The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
19
|
+
The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29) and [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L70) 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 implementation.
|
|
20
20
|
|
|
21
21
|
**Code: Example Injectable**
|
|
22
22
|
```typescript
|
|
@@ -77,7 +77,7 @@ class SpecificService extends BaseService {
|
|
|
77
77
|
}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
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#
|
|
80
|
+
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#L70) decorator denotes a `static` class method that produces an [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29).
|
|
81
81
|
|
|
82
82
|
**Code: Example InjectableFactory**
|
|
83
83
|
```typescript
|
|
@@ -100,10 +100,10 @@ Given the `static` method `initService`, the function will be provided as a vali
|
|
|
100
100
|
|
|
101
101
|
**Code: Example Conditional Dependency**
|
|
102
102
|
```typescript
|
|
103
|
-
import {
|
|
103
|
+
import { Runtime } from '@travetto/runtime';
|
|
104
104
|
import { Inject, Injectable } from '@travetto/di';
|
|
105
105
|
|
|
106
|
-
@Injectable({ enabled:
|
|
106
|
+
@Injectable({ enabled: Runtime.production })
|
|
107
107
|
class ProductionLogger {
|
|
108
108
|
async log() {
|
|
109
109
|
console.log('This will only run in production');
|
|
@@ -125,12 +125,12 @@ class RuntimeService {
|
|
|
125
125
|
|
|
126
126
|
In this example, the enabled flag is specified in relationship to the deployment environment. When coupled with optional properties, and optional chaining, allows for seamless inclusion of optional dependencies at runtime.
|
|
127
127
|
|
|
128
|
-
**Note**: Other modules are able to provide aliases to [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
128
|
+
**Note**: Other modules are able to provide aliases to [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29) that also provide additional functionality. For example, the [Configuration](https://github.com/travetto/travetto/tree/main/module/config#readme "Configuration support") module @Config or the [RESTful API](https://github.com/travetto/travetto/tree/main/module/rest#readme "Declarative api for RESTful APIs with support for the dependency injection module.") module @Controller decorator registers the associated class as an injectable element.
|
|
129
129
|
|
|
130
130
|
## Injection
|
|
131
|
-
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#
|
|
131
|
+
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#L29) instances to your code. There are three primary methods for injection:
|
|
132
132
|
|
|
133
|
-
The [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
133
|
+
The [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29) decorator, which denotes a desire to inject a value directly. These will be set post construction.
|
|
134
134
|
|
|
135
135
|
**Code: Example Injectable with dependencies as Inject fields**
|
|
136
136
|
```typescript
|
|
@@ -148,7 +148,7 @@ class CustomService {
|
|
|
148
148
|
}
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
151
|
+
The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29) constructor params, which will be provided as the instance is being constructed.
|
|
152
152
|
|
|
153
153
|
**Code: Example Injectable with dependencies in constructor**
|
|
154
154
|
```typescript
|
|
@@ -165,7 +165,7 @@ class CustomService {
|
|
|
165
165
|
}
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
Via [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
168
|
+
Via [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L70) params, which are comparable to constructor params
|
|
169
169
|
|
|
170
170
|
**Code: Example InjectableFactory with parameters as dependencies**
|
|
171
171
|
```typescript
|
|
@@ -223,9 +223,9 @@ class Config {
|
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
## Non-Framework Dependencies
|
|
226
|
-
The module is built around the framework's management of class registration, and being able to decorate the code with [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
226
|
+
The module is built around the framework's management of class registration, and being able to decorate the code with [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29) decorators. There may also be a desire to leverage external code and pull it into the dependency injection framework. This could easily be achieved using a wrapper class that is owned by the framework.
|
|
227
227
|
|
|
228
|
-
It is also possible to directly reference external types, and they will be converted into unique symbols. These symbols cannot be used manually, but can be leveraged using [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
228
|
+
It is also possible to directly reference external types, and they will be converted into unique symbols. These symbols cannot be used manually, but can be leveraged using [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29) decorators.
|
|
229
229
|
|
|
230
230
|
**Code: Example External Dependencies**
|
|
231
231
|
```typescript
|
|
@@ -233,6 +233,7 @@ import { EventEmitter } from 'node:events';
|
|
|
233
233
|
import { Writable } from 'node:stream';
|
|
234
234
|
|
|
235
235
|
import { Inject, Injectable, InjectableFactory } from '@travetto/di';
|
|
236
|
+
import { asFull } from '@travetto/runtime';
|
|
236
237
|
|
|
237
238
|
class Source {
|
|
238
239
|
@InjectableFactory()
|
|
@@ -242,12 +243,12 @@ class Source {
|
|
|
242
243
|
|
|
243
244
|
@InjectableFactory(Symbol.for('custom-1'))
|
|
244
245
|
static writable(): Writable {
|
|
245
|
-
return {}
|
|
246
|
+
return asFull({});
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
@InjectableFactory(Symbol.for('custom-2'))
|
|
249
250
|
static writableAlt(): Writable {
|
|
250
|
-
return {}
|
|
251
|
+
return asFull({});
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/di",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.10",
|
|
4
4
|
"description": "Dependency registration/management and injection support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast-transformations",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"directory": "module/di"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/registry": "^5.0.0-rc.
|
|
30
|
+
"@travetto/registry": "^5.0.0-rc.10"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/transformer": "^5.0.0-rc.
|
|
33
|
+
"@travetto/transformer": "^5.0.0-rc.7"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@travetto/transformer": {
|
package/src/decorator.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { asConstructable, asFull, TypedFunction, type Class } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { InjectableFactoryConfig, InjectableConfig, Dependency } from './types';
|
|
4
4
|
import { DependencyRegistry, ResolutionType } from './registry';
|
|
5
5
|
|
|
6
6
|
function collapseConfig<T extends { qualifier?: symbol }>(...args: (symbol | Partial<InjectConfig> | undefined)[]): T {
|
|
7
|
-
|
|
8
|
-
let out = {} as T;
|
|
7
|
+
let out: Partial<T> = {};
|
|
9
8
|
if (args) {
|
|
10
9
|
if (Array.isArray(args)) {
|
|
11
10
|
for (const arg of args) {
|
|
@@ -16,11 +15,10 @@ function collapseConfig<T extends { qualifier?: symbol }>(...args: (symbol | Par
|
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
17
|
} else {
|
|
19
|
-
|
|
20
|
-
out = args as T;
|
|
18
|
+
out = args;
|
|
21
19
|
}
|
|
22
20
|
}
|
|
23
|
-
return out;
|
|
21
|
+
return asFull(out);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
/**
|
|
@@ -30,11 +28,11 @@ function collapseConfig<T extends { qualifier?: symbol }>(...args: (symbol | Par
|
|
|
30
28
|
*/
|
|
31
29
|
export function Injectable(first?: Partial<InjectableConfig> | symbol, ...args: (Partial<InjectableConfig> | undefined)[]) {
|
|
32
30
|
return <T extends Class>(target: T): T => {
|
|
33
|
-
const config =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
DependencyRegistry.registerClass(target, config
|
|
31
|
+
const config = {
|
|
32
|
+
...collapseConfig<Partial<InjectableConfig>>(first, ...args),
|
|
33
|
+
class: target
|
|
34
|
+
};
|
|
35
|
+
DependencyRegistry.registerClass(target, config);
|
|
38
36
|
return target;
|
|
39
37
|
};
|
|
40
38
|
}
|
|
@@ -43,8 +41,7 @@ export type InjectConfig = { qualifier?: symbol, optional?: boolean, resolution?
|
|
|
43
41
|
|
|
44
42
|
export function InjectArgs(configs?: InjectConfig[][]) {
|
|
45
43
|
return <T extends Class>(target: T): void => {
|
|
46
|
-
DependencyRegistry.registerConstructor(target,
|
|
47
|
-
configs?.map(x => collapseConfig(...x)));
|
|
44
|
+
DependencyRegistry.registerConstructor(target, configs?.map(x => collapseConfig(...x)));
|
|
48
45
|
};
|
|
49
46
|
}
|
|
50
47
|
|
|
@@ -56,11 +53,10 @@ export function InjectArgs(configs?: InjectConfig[][]) {
|
|
|
56
53
|
export function Inject(first?: InjectConfig | symbol, ...args: (InjectConfig | undefined)[]) {
|
|
57
54
|
return (target: unknown, propertyKey?: string, idx?: number | PropertyDescriptor): void => {
|
|
58
55
|
if (typeof idx !== 'number') { // Only register if on property
|
|
59
|
-
const config
|
|
56
|
+
const config = collapseConfig<Dependency>(first, ...args);
|
|
60
57
|
|
|
61
58
|
DependencyRegistry.registerProperty(
|
|
62
|
-
|
|
63
|
-
(target as ClassInstance).constructor, propertyKey as string, config as Dependency
|
|
59
|
+
asConstructable(target).constructor, propertyKey!, config
|
|
64
60
|
);
|
|
65
61
|
}
|
|
66
62
|
};
|
|
@@ -72,8 +68,7 @@ export function Inject(first?: InjectConfig | symbol, ...args: (InjectConfig | u
|
|
|
72
68
|
* @augments `@travetto/di:InjectableFactory`
|
|
73
69
|
*/
|
|
74
70
|
export function InjectableFactory(first?: Partial<InjectableFactoryConfig> | symbol, ...args: (Partial<InjectableFactoryConfig> | undefined)[]) {
|
|
75
|
-
|
|
76
|
-
return <T extends Class>(target: T, property: string | symbol, descriptor: TypedPropertyDescriptor<((..._: any[]) => any)>): void => {
|
|
71
|
+
return <T extends Class>(target: T, property: string | symbol, descriptor: TypedPropertyDescriptor<TypedFunction>): void => {
|
|
77
72
|
const config: InjectableFactoryConfig = collapseConfig(first, ...args);
|
|
78
73
|
DependencyRegistry.registerFactory({
|
|
79
74
|
...config,
|
package/src/error.ts
CHANGED
package/src/registry.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Class,
|
|
1
|
+
import { Class, Runtime, asConstructable, castTo, classConstruct, describeFunction, asFull, castKey, TypedFunction } from '@travetto/runtime';
|
|
2
2
|
import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
|
|
3
|
-
import { RuntimeIndex } from '@travetto/manifest';
|
|
4
3
|
|
|
5
4
|
import { Dependency, InjectableConfig, ClassTarget, InjectableFactoryConfig } from './types';
|
|
6
5
|
import { InjectionError } from './error';
|
|
@@ -15,13 +14,11 @@ export type ResolutionType = 'strict' | 'loose' | 'any';
|
|
|
15
14
|
const PrimaryCandidateⲐ = Symbol.for('@travetto/di:primary');
|
|
16
15
|
|
|
17
16
|
function hasPostConstruct(o: unknown): o is { postConstruct: () => Promise<unknown> } {
|
|
18
|
-
|
|
19
|
-
return !!o && !!(o as Record<string, unknown>)['postConstruct'];
|
|
17
|
+
return !!o && typeof o === 'object' && 'postConstruct' in o && typeof o.postConstruct === 'function';
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
function hasPreDestroy(o: unknown): o is { preDestroy: () => unknown } {
|
|
23
|
-
|
|
24
|
-
return !!o && !!(o as Record<string, unknown>)['preDestroy'];
|
|
21
|
+
return !!o && typeof o === 'object' && 'preDestroy' in o && typeof o.preDestroy === 'function';
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
/**
|
|
@@ -70,8 +67,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
70
67
|
} else if (filtered.length > 1) {
|
|
71
68
|
// If dealing with sub types, prioritize exact matches
|
|
72
69
|
const exact = this
|
|
73
|
-
|
|
74
|
-
.getCandidateTypes(target as Class)
|
|
70
|
+
.getCandidateTypes(castTo<Class>(target))
|
|
75
71
|
.filter(x => x.class === target);
|
|
76
72
|
if (exact.length === 1) {
|
|
77
73
|
qualifier = exact[0].qualifier;
|
|
@@ -99,8 +95,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
99
95
|
}
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
|
|
103
|
-
const config = this.get(cls!) as InjectableConfig<T>;
|
|
98
|
+
const config: InjectableConfig<T> = castTo(this.get(cls!));
|
|
104
99
|
return {
|
|
105
100
|
qualifier,
|
|
106
101
|
config,
|
|
@@ -139,15 +134,13 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
139
134
|
*/
|
|
140
135
|
protected async resolveFieldDependencies<T>(config: InjectableConfig<T>, instance: T): Promise<void> {
|
|
141
136
|
const keys = Object.keys(config.dependencies.fields ?? {})
|
|
142
|
-
//
|
|
143
|
-
.filter(k => instance[k as keyof T] === undefined); // Filter out already set ones
|
|
137
|
+
.filter(k => instance[castKey<T>(k)] === undefined); // Filter out already set ones
|
|
144
138
|
|
|
145
139
|
// And auto-wire
|
|
146
140
|
if (keys.length) {
|
|
147
141
|
const deps = await this.fetchDependencies(config, keys.map(x => config.dependencies.fields[x]));
|
|
148
142
|
for (let i = 0; i < keys.length; i++) {
|
|
149
|
-
|
|
150
|
-
instance[keys[i] as keyof T] = deps[i] as T[keyof T];
|
|
143
|
+
instance[castKey<T>(keys[i])] = castTo(deps[i]);
|
|
151
144
|
}
|
|
152
145
|
}
|
|
153
146
|
}
|
|
@@ -164,16 +157,14 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
164
157
|
// Create instance
|
|
165
158
|
const inst = managed.factory ?
|
|
166
159
|
managed.factory(...consValues) :
|
|
167
|
-
|
|
168
|
-
new (managed.class as ConcreteClass<T>)(...consValues);
|
|
160
|
+
classConstruct(managed.class, consValues);
|
|
169
161
|
|
|
170
162
|
// And auto-wire fields
|
|
171
163
|
await this.resolveFieldDependencies(managed, inst);
|
|
172
164
|
|
|
173
165
|
// If factory with field properties on the sub class
|
|
174
166
|
if (managed.factory) {
|
|
175
|
-
|
|
176
|
-
const resolved = this.get((inst as ClassInstance<T>).constructor);
|
|
167
|
+
const resolved = this.get(asConstructable(inst).constructor);
|
|
177
168
|
|
|
178
169
|
if (resolved) {
|
|
179
170
|
await this.resolveFieldDependencies(resolved, inst);
|
|
@@ -200,8 +191,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
200
191
|
}
|
|
201
192
|
|
|
202
193
|
if (this.instancePromises.get(classId)!.has(qualifier)) {
|
|
203
|
-
|
|
204
|
-
return this.instancePromises.get(classId)!.get(qualifier) as unknown as T;
|
|
194
|
+
return castTo(this.instancePromises.get(classId)!.get(qualifier));
|
|
205
195
|
}
|
|
206
196
|
|
|
207
197
|
const instancePromise = this.construct(target, qualifier);
|
|
@@ -237,7 +227,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
237
227
|
|
|
238
228
|
override async init(): Promise<void> {
|
|
239
229
|
await super.init();
|
|
240
|
-
if (
|
|
230
|
+
if (Runtime.dynamic) {
|
|
241
231
|
const { DependencyRegistration } = await import('../support/dynamic.injection');
|
|
242
232
|
DependencyRegistration.init(this);
|
|
243
233
|
}
|
|
@@ -291,27 +281,24 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
291
281
|
if (!this.instances.has(classId) || !this.instances.get(classId)!.has(qualifier)) {
|
|
292
282
|
await this.createInstance(target, qualifier); // Wait for proxy
|
|
293
283
|
}
|
|
294
|
-
|
|
295
|
-
return this.instances.get(classId)!.get(qualifier)! as T;
|
|
284
|
+
return castTo(this.instances.get(classId)!.get(qualifier));
|
|
296
285
|
}
|
|
297
286
|
|
|
298
287
|
/**
|
|
299
288
|
* Get all available candidate types for the target
|
|
300
289
|
*/
|
|
301
|
-
getCandidateTypes<T>(target: Class<
|
|
290
|
+
getCandidateTypes<T, U = T>(target: Class<U>): InjectableConfig<T>[] {
|
|
302
291
|
const targetId = target.Ⲑid;
|
|
303
292
|
const qualifiers = this.targetToClass.get(targetId)!;
|
|
304
293
|
const uniqueQualifiers = qualifiers ? Array.from(new Set(qualifiers.values())) : [];
|
|
305
|
-
|
|
306
|
-
return uniqueQualifiers.map(id => this.get(id)! as InjectableConfig<T>);
|
|
294
|
+
return castTo(uniqueQualifiers.map(id => this.get(id)));
|
|
307
295
|
}
|
|
308
296
|
|
|
309
297
|
/**
|
|
310
298
|
* Get candidate instances by target type, with an optional filter
|
|
311
299
|
*/
|
|
312
300
|
getCandidateInstances<T>(target: Class, predicate?: (cfg: InjectableConfig<T>) => boolean): Promise<T[]> {
|
|
313
|
-
|
|
314
|
-
const inputs = this.getCandidateTypes<T>(target as Class<T>).filter(x => !predicate || predicate(x));
|
|
301
|
+
const inputs = this.getCandidateTypes<T>(target).filter(x => !predicate || predicate(x));
|
|
315
302
|
return Promise.all(inputs.map(l => this.getInstance<T>(l.class, l.qualifier)));
|
|
316
303
|
}
|
|
317
304
|
|
|
@@ -354,9 +341,10 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
354
341
|
}
|
|
355
342
|
if (pConfig.dependencies) {
|
|
356
343
|
config.dependencies = {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
344
|
+
...pConfig.dependencies,
|
|
345
|
+
fields: {
|
|
346
|
+
...pConfig.dependencies.fields
|
|
347
|
+
}
|
|
360
348
|
};
|
|
361
349
|
}
|
|
362
350
|
}
|
|
@@ -367,7 +355,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
367
355
|
registerFactory(config: Omit<InjectableFactoryConfig, 'qualifier'> & {
|
|
368
356
|
id: string;
|
|
369
357
|
qualifier?: undefined | symbol;
|
|
370
|
-
fn:
|
|
358
|
+
fn: TypedFunction;
|
|
371
359
|
}): void {
|
|
372
360
|
const finalConfig: Partial<InjectableConfig> = {};
|
|
373
361
|
|
|
@@ -389,8 +377,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
389
377
|
}
|
|
390
378
|
|
|
391
379
|
// Create mock cls for DI purposes
|
|
392
|
-
|
|
393
|
-
const cls = { Ⲑid: config.id } as Class;
|
|
380
|
+
const cls = asFull<Class>({ Ⲑid: config.id });
|
|
394
381
|
|
|
395
382
|
finalConfig.class = cls;
|
|
396
383
|
|
|
@@ -400,8 +387,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
400
387
|
this.factories.set(config.src.Ⲑid, new Map());
|
|
401
388
|
}
|
|
402
389
|
|
|
403
|
-
|
|
404
|
-
this.factories.get(config.src.Ⲑid)!.set(cls, finalConfig as InjectableConfig);
|
|
390
|
+
this.factories.get(config.src.Ⲑid)!.set(cls, asFull(finalConfig));
|
|
405
391
|
}
|
|
406
392
|
|
|
407
393
|
/**
|
|
@@ -424,18 +410,17 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
424
410
|
onInstallFinalize<T>(cls: Class<T>): InjectableConfig<T> {
|
|
425
411
|
const classId = cls.Ⲑid;
|
|
426
412
|
|
|
427
|
-
|
|
428
|
-
const config = this.getOrCreatePending(cls) as InjectableConfig<T>;
|
|
413
|
+
const config: InjectableConfig<T> = castTo(this.getOrCreatePending(cls));
|
|
429
414
|
|
|
430
415
|
if (!(typeof config.enabled === 'boolean' ? config.enabled : config.enabled())) {
|
|
431
416
|
return config; // Do not setup if disabled
|
|
432
417
|
}
|
|
433
418
|
|
|
434
419
|
// Allow for the factory to fulfill the target
|
|
435
|
-
let parentClass = config.factory ? config.target : Object.getPrototypeOf(cls);
|
|
420
|
+
let parentClass: Function = config.factory ? config.target : Object.getPrototypeOf(cls);
|
|
436
421
|
|
|
437
422
|
if (config.factory) {
|
|
438
|
-
while (
|
|
423
|
+
while (describeFunction(Object.getPrototypeOf(parentClass))?.abstract) {
|
|
439
424
|
parentClass = Object.getPrototypeOf(parentClass);
|
|
440
425
|
}
|
|
441
426
|
if (!this.targetToClass.has(classId)) {
|
|
@@ -465,7 +450,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
465
450
|
}
|
|
466
451
|
}
|
|
467
452
|
|
|
468
|
-
if (
|
|
453
|
+
if (describeFunction(cls)?.abstract) { // Skip out early, only needed to inherit
|
|
469
454
|
return config;
|
|
470
455
|
}
|
|
471
456
|
|
|
@@ -500,7 +485,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
500
485
|
}
|
|
501
486
|
|
|
502
487
|
// If targeting self (default @Injectable behavior)
|
|
503
|
-
if ((classId === targetId || config.factory) && (parentConfig ||
|
|
488
|
+
if ((classId === targetId || config.factory) && (parentConfig || describeFunction(parentClass)?.abstract)) {
|
|
504
489
|
const parentId = parentClass.Ⲑid;
|
|
505
490
|
|
|
506
491
|
if (!this.targetToClass.has(parentId)) {
|
|
@@ -558,8 +543,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
558
543
|
/**
|
|
559
544
|
* Inject fields into instance
|
|
560
545
|
*/
|
|
561
|
-
|
|
562
|
-
async injectFields<T extends { constructor: Class<T> }>(o: T, cls = o.constructor as Class<T>): Promise<void> {
|
|
546
|
+
async injectFields<T extends { constructor: Class<T> }>(o: T, cls = o.constructor): Promise<void> {
|
|
563
547
|
this.verifyInitialized();
|
|
564
548
|
// Compute fields to be auto-wired
|
|
565
549
|
return await this.resolveFieldDependencies(this.get(cls), o);
|
|
@@ -573,8 +557,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
573
557
|
): Promise<Awaited<ReturnType<T['run']>>> {
|
|
574
558
|
await RootRegistry.init();
|
|
575
559
|
const inst = await this.getInstance<T>(cls);
|
|
576
|
-
|
|
577
|
-
return inst.run(...args) as Awaited<ReturnType<T['run']>>;
|
|
560
|
+
return castTo<Awaited<ReturnType<T['run']>>>(inst.run(...args));
|
|
578
561
|
}
|
|
579
562
|
}
|
|
580
563
|
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Class,
|
|
2
|
-
import { RuntimeIndex } from '@travetto/manifest';
|
|
1
|
+
import { Class, describeFunction } from '@travetto/runtime';
|
|
3
2
|
import { RetargettingProxy } from '@travetto/registry';
|
|
4
3
|
|
|
5
4
|
import type { DependencyRegistry, ResolutionType, Resolved } from '../src/registry';
|
|
@@ -21,7 +20,7 @@ class $DynamicDependencyRegistry {
|
|
|
21
20
|
*/
|
|
22
21
|
proxyInstance<T>(target: ClassTarget<T>, qual: symbol | undefined, instance: T): T {
|
|
23
22
|
const { qualifier, id: classId } = this.#registryResolveTarget(target, qual);
|
|
24
|
-
let proxy: RetargettingProxy<
|
|
23
|
+
let proxy: RetargettingProxy<unknown>;
|
|
25
24
|
|
|
26
25
|
if (!this.#proxies.has(classId)) {
|
|
27
26
|
this.#proxies.set(classId, new Map());
|
|
@@ -34,18 +33,16 @@ class $DynamicDependencyRegistry {
|
|
|
34
33
|
console.debug('Registering proxy', { id: target.Ⲑid, qualifier: qualifier.toString() });
|
|
35
34
|
}
|
|
36
35
|
} else {
|
|
37
|
-
|
|
38
|
-
proxy = this.#proxies.get(classId)!.get(qualifier) as RetargettingProxy<T>;
|
|
36
|
+
proxy = this.#proxies.get(classId)!.get(qualifier)!;
|
|
39
37
|
proxy.setTarget(instance);
|
|
40
38
|
if (this.#registry.trace) {
|
|
41
39
|
console.debug('Updating target', {
|
|
42
|
-
|
|
43
|
-
id: target.Ⲑid, qualifier: qualifier.toString(), instanceType: (instance as unknown as ClassInstance<T>).constructor.name as string
|
|
40
|
+
id: target.Ⲑid, qualifier: qualifier.toString(), instanceType: target.name
|
|
44
41
|
});
|
|
45
42
|
}
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
return proxy.get();
|
|
45
|
+
return proxy.get<T>();
|
|
49
46
|
}
|
|
50
47
|
|
|
51
48
|
/**
|
|
@@ -69,7 +66,7 @@ class $DynamicDependencyRegistry {
|
|
|
69
66
|
const classId = cls.Ⲑid;
|
|
70
67
|
|
|
71
68
|
if (
|
|
72
|
-
!
|
|
69
|
+
!describeFunction(cls)?.abstract &&
|
|
73
70
|
this.#proxies.has(classId) &&
|
|
74
71
|
this.#proxies.get(classId)!.has(config.qualifier)
|
|
75
72
|
) {
|
package/support/test/suite.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class, ClassInstance } from '@travetto/
|
|
1
|
+
import { castTo, Class, ClassInstance } from '@travetto/runtime';
|
|
2
2
|
import { RootRegistry } from '@travetto/registry';
|
|
3
3
|
import { SuiteRegistry } from '@travetto/test';
|
|
4
4
|
|
|
@@ -11,9 +11,9 @@ export function InjectableSuite() {
|
|
|
11
11
|
return (target: Class) => {
|
|
12
12
|
SuiteRegistry.registerPendingListener(
|
|
13
13
|
target,
|
|
14
|
-
async function (this:
|
|
14
|
+
async function (this: unknown) {
|
|
15
15
|
await RootRegistry.init();
|
|
16
|
-
await DependencyRegistry.injectFields(this
|
|
16
|
+
await DependencyRegistry.injectFields(castTo<ClassInstance>(this), target);
|
|
17
17
|
},
|
|
18
18
|
'beforeEach'
|
|
19
19
|
);
|
|
@@ -21,9 +21,11 @@ export class InjectableTransformer {
|
|
|
21
21
|
return [];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const args: ts.Expression[] = [];
|
|
25
|
+
|
|
26
|
+
if (existing && ts.isCallExpression(existing.expression)) {
|
|
27
|
+
args.push(...existing.expression.arguments);
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
const payload: { target?: unknown, qualifier?: unknown, optional?: boolean } = {};
|
|
29
31
|
|
|
@@ -140,6 +142,11 @@ export class InjectableTransformer {
|
|
|
140
142
|
return node;
|
|
141
143
|
}
|
|
142
144
|
|
|
145
|
+
const parent = node.parent;
|
|
146
|
+
if (ts.isObjectLiteralExpression(parent)) {
|
|
147
|
+
return node;
|
|
148
|
+
}
|
|
149
|
+
|
|
143
150
|
const dec = dm?.dec;
|
|
144
151
|
|
|
145
152
|
// Extract config
|
|
@@ -148,8 +155,7 @@ export class InjectableTransformer {
|
|
|
148
155
|
// Read target from config or resolve
|
|
149
156
|
const config: { dependencies: unknown[], target?: unknown, qualifier?: unknown, src?: unknown } = {
|
|
150
157
|
dependencies,
|
|
151
|
-
|
|
152
|
-
src: (node.parent as ts.ClassDeclaration).name,
|
|
158
|
+
src: parent.name,
|
|
153
159
|
};
|
|
154
160
|
let ret = state.resolveReturnType(node);
|
|
155
161
|
if (ret.key === 'literal' && ret.ctor === Promise && ret.typeArguments) {
|