@travetto/di 3.0.0-rc.4 → 3.0.0-rc.7
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 +45 -9
- package/{index.ts → __index__.ts} +0 -0
- package/package.json +14 -14
- package/src/decorator.ts +5 -6
- package/src/error.ts +1 -1
- package/src/internal/types.ts +1 -0
- package/src/registry.ts +55 -38
- package/src/types.ts +6 -1
- package/support/dynamic.injection.ts +92 -73
- package/support/main.invoke.ts +17 -0
- package/{test-support → support/test}/suite.ts +1 -1
- package/support/transformer.injectable.ts +11 -12
- package/support/invoke.ts +0 -16
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- This file was generated by @travetto/doc and should not be modified directly -->
|
|
2
|
-
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/di/
|
|
2
|
+
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/di/DOC.ts and execute "npx trv doc" to rebuild -->
|
|
3
3
|
# Dependency Injection
|
|
4
4
|
## Dependency registration/management and injection support.
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@ npm install @travetto/di
|
|
|
11
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#L31) and [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L74) 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.
|
|
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#L74) decorator denotes a `static` class method that produces an [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L31).
|
|
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#L31) that also provide additional functionality. For example, the [Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.js#L10) 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.
|
|
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#L31) 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#L31) 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#L31) 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#L31) 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#L74) params, which are comparable to constructor params
|
|
138
138
|
|
|
139
139
|
**Code: Example InjectableFactory with parameters as dependencies**
|
|
140
140
|
```typescript
|
|
@@ -210,3 +210,39 @@ class ManualLookup {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
```
|
|
213
|
+
|
|
214
|
+
Additionally, support for interfaces (over class inheritance) is provided, but requires binding the interface to a concrete class as the interface does not exist at runtime.
|
|
215
|
+
|
|
216
|
+
**Code: Example Interface Injection**
|
|
217
|
+
```typescript
|
|
218
|
+
import { DependencyRegistry, Inject, Injectable, InjectableFactory } from '@travetto/di';
|
|
219
|
+
|
|
220
|
+
class TargetConcrete { }
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @concrete .:TargetConcrete
|
|
224
|
+
*/
|
|
225
|
+
export interface ServiceContract {
|
|
226
|
+
deleteUser(userId: string): Promise<void>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
class MyCustomService implements ServiceContract {
|
|
230
|
+
async deleteUser(userId: string): Promise<void> {
|
|
231
|
+
// Do something
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@Injectable()
|
|
236
|
+
class SpecificService {
|
|
237
|
+
|
|
238
|
+
@Inject()
|
|
239
|
+
service: ServiceContract;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
class ManualInvocationOfInterface {
|
|
243
|
+
@InjectableFactory()
|
|
244
|
+
static getCustomService(): Promise<ServiceContract> {
|
|
245
|
+
return DependencyRegistry.getInstance<ServiceContract>(TargetConcrete);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/di",
|
|
3
|
-
"
|
|
4
|
-
"version": "3.0.0-rc.4",
|
|
3
|
+
"version": "3.0.0-rc.7",
|
|
5
4
|
"description": "Dependency registration/management and injection support.",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"ast-transformations",
|
|
@@ -18,27 +17,28 @@
|
|
|
18
17
|
"name": "Travetto Framework"
|
|
19
18
|
},
|
|
20
19
|
"files": [
|
|
21
|
-
"
|
|
20
|
+
"__index__.ts",
|
|
22
21
|
"src",
|
|
23
|
-
"support"
|
|
24
|
-
"test-support"
|
|
22
|
+
"support"
|
|
25
23
|
],
|
|
26
|
-
"main": "
|
|
24
|
+
"main": "__index__.ts",
|
|
27
25
|
"repository": {
|
|
28
26
|
"url": "https://github.com/travetto/travetto.git",
|
|
29
27
|
"directory": "module/di"
|
|
30
28
|
},
|
|
31
29
|
"dependencies": {
|
|
32
|
-
"@travetto/
|
|
33
|
-
"@travetto/registry": "^3.0.0-rc.4"
|
|
30
|
+
"@travetto/registry": "^3.0.0-rc.7"
|
|
34
31
|
},
|
|
35
|
-
"
|
|
36
|
-
"@travetto/
|
|
37
|
-
"@travetto/schema": "^3.0.0-rc.4"
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@travetto/transformer": "^3.0.0-rc.7"
|
|
38
34
|
},
|
|
39
|
-
"
|
|
40
|
-
"@travetto/
|
|
41
|
-
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"@travetto/transformer": {
|
|
37
|
+
"optional": true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"travetto": {
|
|
41
|
+
"displayName": "Dependency Injection"
|
|
42
42
|
},
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
package/src/decorator.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Class, ClassInstance } from '@travetto/base';
|
|
2
|
-
import { MethodDescriptor } from '@travetto/base/src/internal/types';
|
|
1
|
+
import type { MethodDescriptor, Class, ClassInstance } from '@travetto/base';
|
|
3
2
|
|
|
4
3
|
import { InjectableFactoryConfig, InjectableConfig, Dependency } from './types';
|
|
5
4
|
import { DependencyRegistry, ResolutionType } from './registry';
|
|
@@ -27,7 +26,7 @@ function collapseConfig<T extends { qualifier?: symbol }>(...args: (symbol | Par
|
|
|
27
26
|
/**
|
|
28
27
|
* Indicate that a class is able to be injected
|
|
29
28
|
*
|
|
30
|
-
* @augments `@
|
|
29
|
+
* @augments `@travetto/di:Injectable`
|
|
31
30
|
*/
|
|
32
31
|
export function Injectable(first?: Partial<InjectableConfig> | symbol, ...args: (Partial<InjectableConfig> | undefined)[]) {
|
|
33
32
|
return <T extends Class>(target: T): T => {
|
|
@@ -52,7 +51,7 @@ export function InjectArgs(configs?: InjectConfig[][]) {
|
|
|
52
51
|
/**
|
|
53
52
|
* Indicate that a field is able to be injected
|
|
54
53
|
*
|
|
55
|
-
* @augments `@
|
|
54
|
+
* @augments `@travetto/di:Inject`
|
|
56
55
|
*/
|
|
57
56
|
export function Inject(first?: InjectConfig | symbol, ...args: (InjectConfig | undefined)[]) {
|
|
58
57
|
return (target: unknown, propertyKey: string | symbol, idx?: number | PropertyDescriptor): void => {
|
|
@@ -70,7 +69,7 @@ export function Inject(first?: InjectConfig | symbol, ...args: (InjectConfig | u
|
|
|
70
69
|
/**
|
|
71
70
|
* Identifies a static method that is able to produce a dependency
|
|
72
71
|
*
|
|
73
|
-
* @augments `@
|
|
72
|
+
* @augments `@travetto/di:InjectableFactory`
|
|
74
73
|
*/
|
|
75
74
|
export function InjectableFactory(first?: Partial<InjectableFactoryConfig> | symbol, ...args: (Partial<InjectableFactoryConfig> | undefined)[]) {
|
|
76
75
|
return <T extends Class>(target: T, property: string | symbol, descriptor: MethodDescriptor): void => {
|
|
@@ -79,7 +78,7 @@ export function InjectableFactory(first?: Partial<InjectableFactoryConfig> | sym
|
|
|
79
78
|
...config,
|
|
80
79
|
dependencies: config.dependencies?.map(x => Array.isArray(x) ? collapseConfig(...x) : collapseConfig(x)),
|
|
81
80
|
fn: descriptor.value!,
|
|
82
|
-
id: `${target
|
|
81
|
+
id: `${target.Ⲑid}#${property.toString()}`
|
|
83
82
|
});
|
|
84
83
|
};
|
|
85
84
|
}
|
package/src/error.ts
CHANGED
|
@@ -7,6 +7,6 @@ function getName(symbol: symbol): string {
|
|
|
7
7
|
|
|
8
8
|
export class InjectionError extends AppError {
|
|
9
9
|
constructor(message: string, target: ClassTarget, qualifiers?: symbol[]) {
|
|
10
|
-
super(`${message}: [${target
|
|
10
|
+
super(`${message}: [${target.Ⲑid}]${qualifiers ? `[${qualifiers.map(getName)}]` : ''}`, 'notfound');
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class AutoCreateTarget { }
|
package/src/registry.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { Class, ClassInstance, ConcreteClass } from '@travetto/base';
|
|
1
|
+
import { Class, ClassInstance, ConcreteClass, GlobalEnv } from '@travetto/base';
|
|
2
2
|
import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
|
|
3
|
-
import {
|
|
3
|
+
import { RootIndex } from '@travetto/manifest';
|
|
4
4
|
|
|
5
5
|
import { Dependency, InjectableConfig, ClassTarget, InjectableFactoryConfig } from './types';
|
|
6
6
|
import { InjectionError } from './error';
|
|
7
|
+
import { AutoCreateTarget } from './internal/types';
|
|
7
8
|
|
|
8
9
|
type TargetId = string;
|
|
9
10
|
type ClassId = string;
|
|
10
|
-
type Resolved<T> = { config: InjectableConfig<T>, qualifier: symbol, id: string };
|
|
11
|
+
export type Resolved<T> = { config: InjectableConfig<T>, qualifier: symbol, id: string };
|
|
11
12
|
|
|
12
13
|
export type ResolutionType = 'strict' | 'loose' | 'any';
|
|
13
14
|
|
|
14
|
-
const PrimaryCandidateⲐ = Symbol.for('@
|
|
15
|
+
const PrimaryCandidateⲐ = Symbol.for('@travetto/di:primary');
|
|
15
16
|
|
|
16
17
|
function hasPostConstruct(o: unknown): o is { postConstruct: () => Promise<unknown> } {
|
|
17
18
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -26,7 +27,6 @@ function hasPreDestroy(o: unknown): o is { preDestroy: () => unknown } {
|
|
|
26
27
|
/**
|
|
27
28
|
* Dependency registry
|
|
28
29
|
*/
|
|
29
|
-
@Dynamic('@travetto/di/support/dynamic.injection')
|
|
30
30
|
class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
31
31
|
protected pendingFinalize: Class[] = [];
|
|
32
32
|
|
|
@@ -50,7 +50,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
50
50
|
* @param qualifier
|
|
51
51
|
*/
|
|
52
52
|
protected resolveTarget<T>(target: ClassTarget<T>, qualifier?: symbol, resolution?: ResolutionType): Resolved<T> {
|
|
53
|
-
const qualifiers = this.targetToClass.get(target
|
|
53
|
+
const qualifiers = this.targetToClass.get(target.Ⲑid) ?? new Map<symbol, string>();
|
|
54
54
|
|
|
55
55
|
let cls: string | undefined;
|
|
56
56
|
|
|
@@ -90,7 +90,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
90
90
|
throw new InjectionError('Dependency not found', target);
|
|
91
91
|
} else if (!qualifiers.has(qualifier)) {
|
|
92
92
|
if (!this.defaultSymbols.has(qualifier) && resolution === 'loose') {
|
|
93
|
-
console.debug('Unable to find specific dependency, falling back to general instance', { qualifier, target: target
|
|
93
|
+
console.debug('Unable to find specific dependency, falling back to general instance', { qualifier, target: target.Ⲑid });
|
|
94
94
|
return this.resolveTarget(target);
|
|
95
95
|
}
|
|
96
96
|
throw new InjectionError('Dependency not found', target, [qualifier]);
|
|
@@ -104,7 +104,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
104
104
|
return {
|
|
105
105
|
qualifier,
|
|
106
106
|
config,
|
|
107
|
-
id: (config.factory ? config.target : config.class)
|
|
107
|
+
id: (config.factory ? config.target : config.class).Ⲑid
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -124,7 +124,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
124
124
|
return undefined;
|
|
125
125
|
} else {
|
|
126
126
|
if (err && err instanceof Error) {
|
|
127
|
-
err.message = `${err.message} via=${managed.class
|
|
127
|
+
err.message = `${err.message} via=${managed.class.Ⲑid}`;
|
|
128
128
|
}
|
|
129
129
|
throw err;
|
|
130
130
|
}
|
|
@@ -221,7 +221,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
221
221
|
* Destroy an instance
|
|
222
222
|
*/
|
|
223
223
|
protected destroyInstance(cls: Class, qualifier: symbol): void {
|
|
224
|
-
const classId = cls
|
|
224
|
+
const classId = cls.Ⲑid;
|
|
225
225
|
|
|
226
226
|
const activeInstance = this.instances.get(classId)!.get(qualifier);
|
|
227
227
|
if (hasPreDestroy(activeInstance)) {
|
|
@@ -231,8 +231,20 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
231
231
|
this.defaultSymbols.delete(qualifier);
|
|
232
232
|
this.instances.get(classId)!.delete(qualifier);
|
|
233
233
|
this.instancePromises.get(classId)!.delete(qualifier);
|
|
234
|
-
this.classToTarget.get(cls
|
|
235
|
-
console.debug('On uninstall', { id: cls
|
|
234
|
+
this.classToTarget.get(cls.Ⲑid)!.delete(qualifier);
|
|
235
|
+
console.debug('On uninstall', { id: cls.Ⲑid, qualifier: qualifier.toString(), classId });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
override async init(): Promise<void> {
|
|
239
|
+
await super.init();
|
|
240
|
+
if (GlobalEnv.dynamic) {
|
|
241
|
+
const { DependencyRegistration } = await import('../support/dynamic.injection.js');
|
|
242
|
+
DependencyRegistration.init(this);
|
|
243
|
+
}
|
|
244
|
+
// Allow for auto-creation
|
|
245
|
+
for (const cfg of await this.getCandidateTypes(AutoCreateTarget)) {
|
|
246
|
+
await this.getInstance(cfg.class, cfg.qualifier);
|
|
247
|
+
}
|
|
236
248
|
}
|
|
237
249
|
|
|
238
250
|
/**
|
|
@@ -286,7 +298,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
286
298
|
* Get all available candidate types for the target
|
|
287
299
|
*/
|
|
288
300
|
getCandidateTypes<T>(target: Class<T>): InjectableConfig[] {
|
|
289
|
-
const targetId = target
|
|
301
|
+
const targetId = target.Ⲑid;
|
|
290
302
|
const qualifiers = this.targetToClass.get(targetId)!;
|
|
291
303
|
const uniqueQualifiers = qualifiers ? Array.from(new Set(qualifiers.values())) : [];
|
|
292
304
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -316,7 +328,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
316
328
|
const config = this.getOrCreatePending(pConfig.class ?? cls);
|
|
317
329
|
|
|
318
330
|
config.class = cls;
|
|
319
|
-
config.qualifier = pConfig.qualifier ?? config.qualifier ?? Symbol.for(cls
|
|
331
|
+
config.qualifier = pConfig.qualifier ?? config.qualifier ?? Symbol.for(cls.Ⲑid);
|
|
320
332
|
if (pConfig.interfaces) {
|
|
321
333
|
config.interfaces?.push(...pConfig.interfaces);
|
|
322
334
|
}
|
|
@@ -366,18 +378,18 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
366
378
|
|
|
367
379
|
// Create mock cls for DI purposes
|
|
368
380
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
369
|
-
const cls = {
|
|
381
|
+
const cls = { Ⲑid: config.id } as Class;
|
|
370
382
|
|
|
371
383
|
finalConfig.class = cls;
|
|
372
384
|
|
|
373
385
|
this.registerClass(cls, finalConfig);
|
|
374
386
|
|
|
375
|
-
if (!this.factories.has(config.src
|
|
376
|
-
this.factories.set(config.src
|
|
387
|
+
if (!this.factories.has(config.src.Ⲑid)) {
|
|
388
|
+
this.factories.set(config.src.Ⲑid, new Map());
|
|
377
389
|
}
|
|
378
390
|
|
|
379
391
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
380
|
-
this.factories.get(config.src
|
|
392
|
+
this.factories.get(config.src.Ⲑid)!.set(cls, finalConfig as InjectableConfig);
|
|
381
393
|
}
|
|
382
394
|
|
|
383
395
|
/**
|
|
@@ -387,8 +399,8 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
387
399
|
super.onInstall(cls, e);
|
|
388
400
|
|
|
389
401
|
// Install factories separate from classes
|
|
390
|
-
if (this.factories.has(cls
|
|
391
|
-
for (const fact of this.factories.get(cls
|
|
402
|
+
if (this.factories.has(cls.Ⲑid)) {
|
|
403
|
+
for (const fact of this.factories.get(cls.Ⲑid)!.keys()) {
|
|
392
404
|
this.onInstall(fact, e);
|
|
393
405
|
}
|
|
394
406
|
}
|
|
@@ -398,7 +410,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
398
410
|
* Handle installing a class
|
|
399
411
|
*/
|
|
400
412
|
onInstallFinalize<T>(cls: Class<T>): InjectableConfig<T> {
|
|
401
|
-
const classId = cls
|
|
413
|
+
const classId = cls.Ⲑid;
|
|
402
414
|
|
|
403
415
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
404
416
|
const config = this.getOrCreatePending(cls) as InjectableConfig<T>;
|
|
@@ -407,12 +419,17 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
407
419
|
let parentClass = config.factory ? config.target : Object.getPrototypeOf(cls);
|
|
408
420
|
|
|
409
421
|
if (config.factory) {
|
|
410
|
-
while (Object.getPrototypeOf(parentClass)
|
|
422
|
+
while (RootIndex.getFunctionMetadata(Object.getPrototypeOf(parentClass))?.abstract) {
|
|
411
423
|
parentClass = Object.getPrototypeOf(parentClass);
|
|
412
424
|
}
|
|
425
|
+
if (!this.targetToClass.has(classId)) {
|
|
426
|
+
this.targetToClass.set(classId, new Map());
|
|
427
|
+
}
|
|
428
|
+
// Make explicitly discoverable as self
|
|
429
|
+
this.targetToClass.get(classId)?.set(config.qualifier, classId);
|
|
413
430
|
}
|
|
414
431
|
|
|
415
|
-
const parentConfig = this.get(parentClass
|
|
432
|
+
const parentConfig = this.get(parentClass.Ⲑid);
|
|
416
433
|
|
|
417
434
|
if (parentConfig) {
|
|
418
435
|
config.dependencies.fields = {
|
|
@@ -432,7 +449,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
432
449
|
}
|
|
433
450
|
}
|
|
434
451
|
|
|
435
|
-
if (cls
|
|
452
|
+
if (RootIndex.getFunctionMetadata(cls)?.abstract) { // Skip out early, only needed to inherit
|
|
436
453
|
return config;
|
|
437
454
|
}
|
|
438
455
|
|
|
@@ -440,13 +457,13 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
440
457
|
this.classToTarget.set(classId, new Map());
|
|
441
458
|
}
|
|
442
459
|
|
|
443
|
-
const targetId = config.target
|
|
460
|
+
const targetId = config.target.Ⲑid;
|
|
444
461
|
|
|
445
462
|
if (!this.targetToClass.has(targetId)) {
|
|
446
463
|
this.targetToClass.set(targetId, new Map());
|
|
447
464
|
}
|
|
448
465
|
|
|
449
|
-
if (config.qualifier === Symbol.for(cls
|
|
466
|
+
if (config.qualifier === Symbol.for(cls.Ⲑid)) {
|
|
450
467
|
this.defaultSymbols.add(config.qualifier);
|
|
451
468
|
}
|
|
452
469
|
|
|
@@ -455,20 +472,20 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
455
472
|
|
|
456
473
|
// If aliased
|
|
457
474
|
for (const el of config.interfaces) {
|
|
458
|
-
if (!this.targetToClass.has(el
|
|
459
|
-
this.targetToClass.set(el
|
|
475
|
+
if (!this.targetToClass.has(el.Ⲑid)) {
|
|
476
|
+
this.targetToClass.set(el.Ⲑid, new Map());
|
|
460
477
|
}
|
|
461
|
-
this.targetToClass.get(el
|
|
462
|
-
this.classToTarget.get(classId)!.set(Symbol.for(el
|
|
478
|
+
this.targetToClass.get(el.Ⲑid)!.set(config.qualifier, classId);
|
|
479
|
+
this.classToTarget.get(classId)!.set(Symbol.for(el.Ⲑid), el.Ⲑid);
|
|
463
480
|
|
|
464
481
|
if (config.primary && (classId === targetId || config.factory)) {
|
|
465
|
-
this.targetToClass.get(el
|
|
482
|
+
this.targetToClass.get(el.Ⲑid)!.set(PrimaryCandidateⲐ, classId);
|
|
466
483
|
}
|
|
467
484
|
}
|
|
468
485
|
|
|
469
486
|
// If targeting self (default @Injectable behavior)
|
|
470
|
-
if ((classId === targetId || config.factory) && (parentConfig || parentClass
|
|
471
|
-
const parentId = parentClass
|
|
487
|
+
if ((classId === targetId || config.factory) && (parentConfig || RootIndex.getFunctionMetadata(parentClass)?.abstract)) {
|
|
488
|
+
const parentId = parentClass.Ⲑid;
|
|
472
489
|
|
|
473
490
|
if (!this.targetToClass.has(parentId)) {
|
|
474
491
|
this.targetToClass.set(parentId, new Map());
|
|
@@ -495,10 +512,10 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
495
512
|
// Register primary if only one interface provided and no parent config
|
|
496
513
|
if (config.interfaces.length === 1 && !parentConfig) {
|
|
497
514
|
const [primaryInterface] = config.interfaces;
|
|
498
|
-
if (!this.targetToClass.has(primaryInterface
|
|
499
|
-
this.targetToClass.set(primaryInterface
|
|
515
|
+
if (!this.targetToClass.has(primaryInterface.Ⲑid)) {
|
|
516
|
+
this.targetToClass.set(primaryInterface.Ⲑid, new Map());
|
|
500
517
|
}
|
|
501
|
-
this.targetToClass.get(primaryInterface
|
|
518
|
+
this.targetToClass.get(primaryInterface.Ⲑid)!.set(PrimaryCandidateⲐ, classId);
|
|
502
519
|
}
|
|
503
520
|
}
|
|
504
521
|
|
|
@@ -509,9 +526,9 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
509
526
|
* Handle uninstalling a class
|
|
510
527
|
*/
|
|
511
528
|
override onUninstallFinalize(cls: Class): void {
|
|
512
|
-
const classId = cls
|
|
529
|
+
const classId = cls.Ⲑid;
|
|
513
530
|
|
|
514
|
-
if (!this.classToTarget.has(cls
|
|
531
|
+
if (!this.classToTarget.has(cls.Ⲑid)) {
|
|
515
532
|
return;
|
|
516
533
|
}
|
|
517
534
|
|
package/src/types.ts
CHANGED
|
@@ -77,4 +77,9 @@ export interface InjectableFactoryConfig<T = unknown> extends Core<T> {
|
|
|
77
77
|
* List of all dependencies as function arguments
|
|
78
78
|
*/
|
|
79
79
|
dependencies?: Dependency[];
|
|
80
|
-
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @concrete ./internal/types:AutoCreateTarget
|
|
84
|
+
*/
|
|
85
|
+
export interface AutoCreate { }
|
|
@@ -1,95 +1,114 @@
|
|
|
1
|
-
import { RetargettingProxy } from '@travetto/base
|
|
2
|
-
import {
|
|
1
|
+
import { RetargettingProxy, Class, ClassInstance } from '@travetto/base';
|
|
2
|
+
import { RootIndex } from '@travetto/manifest';
|
|
3
3
|
|
|
4
|
-
import type { DependencyRegistry } from '../src/registry';
|
|
4
|
+
import type { DependencyRegistry, ResolutionType, Resolved } from '../src/registry';
|
|
5
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
|
-
|
|
10
|
+
class $DynamicDependencyRegistry {
|
|
11
|
+
#proxies = new Map<string, Map<symbol | undefined, RetargettingProxy<unknown>>>();
|
|
12
|
+
#registry: typeof DependencyRegistry;
|
|
13
|
+
#registryCreateInstance: <T>(target: ClassTarget<T>, qualifier: symbol) => Promise<T>;
|
|
14
|
+
#registryResolveTarget: <T>(target: ClassTarget<T>, qualifier?: symbol, resolution?: ResolutionType) => Resolved<T>;
|
|
15
|
+
#registryOnInstallFinalize: <T>(target: Class<T>) => InjectableConfig<T>;
|
|
16
|
+
#registryDestroyInstance: <T>(target: Class<T>, qualifier: symbol) => void;
|
|
17
|
+
#registryOnReset: () => void;
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
|
-
*
|
|
20
|
+
* Proxy the created instance
|
|
14
21
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
proxyInstance<T>(target: ClassTarget<T>, qual: symbol | undefined, instance: T): T {
|
|
23
|
+
const { qualifier, id: classId } = this.#registryResolveTarget(target, qual);
|
|
24
|
+
let proxy: RetargettingProxy<T>;
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
protected proxyInstance<T>(target: ClassTarget<T>, qual: symbol | undefined, instance: T): T {
|
|
22
|
-
const { qualifier, id: classId } = this.resolveTarget(target, qual);
|
|
23
|
-
let proxy: RetargettingProxy<T>;
|
|
24
|
-
|
|
25
|
-
if (!this.#proxies.has(classId)) {
|
|
26
|
-
this.#proxies.set(classId, new Map());
|
|
27
|
-
}
|
|
26
|
+
if (!this.#proxies.has(classId)) {
|
|
27
|
+
this.#proxies.set(classId, new Map());
|
|
28
|
+
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
if (!this.#proxies.get(classId)!.has(qualifier)) {
|
|
31
|
+
proxy = new RetargettingProxy<T>(instance);
|
|
32
|
+
this.#proxies.get(classId)!.set(qualifier, proxy);
|
|
33
|
+
console.debug('Registering proxy', { id: target.Ⲑid, qualifier: qualifier.toString() });
|
|
34
|
+
} else {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
36
|
+
proxy = this.#proxies.get(classId)!.get(qualifier) as RetargettingProxy<T>;
|
|
37
|
+
proxy.setTarget(instance);
|
|
38
|
+
console.debug('Updating target', {
|
|
34
39
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
console.debug('Updating target', {
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
39
|
-
id: target.ᚕid, qualifier: qualifier.toString(), instanceType: (instance as unknown as ClassInstance<T>).constructor.name as string
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return proxy.get();
|
|
40
|
+
id: target.Ⲑid, qualifier: qualifier.toString(), instanceType: (instance as unknown as ClassInstance<T>).constructor.name as string
|
|
41
|
+
});
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*/
|
|
49
|
-
protected async createInstance<T>(target: ClassTarget<T>, qualifier: symbol): Promise<T> {
|
|
50
|
-
const instance = await super.createInstance(target, qualifier);
|
|
51
|
-
const classId = this.resolveTarget(target, qualifier).id;
|
|
52
|
-
// Reset as proxy instance
|
|
53
|
-
const proxied = this.proxyInstance<T>(target, qualifier, instance);
|
|
54
|
-
this.instances.get(classId)!.set(qualifier, proxied);
|
|
55
|
-
return proxied;
|
|
56
|
-
}
|
|
44
|
+
return proxy.get();
|
|
45
|
+
}
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Create instance and wrap in a proxy
|
|
49
|
+
*/
|
|
50
|
+
async createInstance<T>(target: ClassTarget<T>, qualifier: symbol): Promise<T> {
|
|
51
|
+
const instance = await this.#registryCreateInstance(target, qualifier);
|
|
52
|
+
const classId = this.#registryResolveTarget(target, qualifier).id;
|
|
53
|
+
// Reset as proxy instance
|
|
54
|
+
const proxied = this.proxyInstance<T>(target, qualifier, instance);
|
|
55
|
+
this.#registry['instances'].get(classId)!.set(qualifier, proxied);
|
|
56
|
+
return proxied;
|
|
57
|
+
}
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
process.nextTick(() => this.createInstance(config.target, config.qualifier));
|
|
74
|
-
}
|
|
59
|
+
/**
|
|
60
|
+
* Reload proxy if in watch mode
|
|
61
|
+
*/
|
|
62
|
+
onInstallFinalize<T>(cls: Class<T>): InjectableConfig<T> {
|
|
63
|
+
const config = this.#registryOnInstallFinalize(cls);
|
|
64
|
+
// If already loaded, reload
|
|
65
|
+
const classId = cls.Ⲑid;
|
|
75
66
|
|
|
76
|
-
|
|
67
|
+
if (
|
|
68
|
+
!RootIndex.getFunctionMetadata(cls)?.abstract &&
|
|
69
|
+
this.#proxies.has(classId) &&
|
|
70
|
+
this.#proxies.get(classId)!.has(config.qualifier)
|
|
71
|
+
) {
|
|
72
|
+
console.debug('Reloading on next tick');
|
|
73
|
+
// Timing matters due to create instance being asynchronous
|
|
74
|
+
process.nextTick(() => this.createInstance(config.target, config.qualifier));
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const proxy = this.#proxies.get(classId)!.get(qualifier);
|
|
82
|
-
super.destroyInstance(cls, qualifier);
|
|
83
|
-
if (proxy) {
|
|
84
|
-
proxy.setTarget(null);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
77
|
+
return config;
|
|
78
|
+
}
|
|
87
79
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
destroyInstance(cls: Class, qualifier: symbol): void {
|
|
81
|
+
const classId = cls.Ⲑid;
|
|
82
|
+
const proxy = this.#proxies.get(classId)!.get(qualifier);
|
|
83
|
+
this.#registryDestroyInstance(cls, qualifier);
|
|
84
|
+
if (proxy) {
|
|
85
|
+
proxy.setTarget(null);
|
|
91
86
|
}
|
|
92
|
-
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onReset(): void {
|
|
90
|
+
this.#registryOnReset();
|
|
91
|
+
this.#proxies.clear();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
register(registry: typeof DependencyRegistry): void {
|
|
95
|
+
this.#registry = registry;
|
|
96
|
+
this.#registryCreateInstance = registry['createInstance'].bind(registry);
|
|
97
|
+
this.#registryResolveTarget = registry['resolveTarget'].bind(registry);
|
|
98
|
+
this.#registryOnInstallFinalize = registry['onInstallFinalize'].bind(registry);
|
|
99
|
+
this.#registryDestroyInstance = registry['destroyInstance'].bind(registry);
|
|
100
|
+
this.#registryOnReset = registry['onReset'].bind(registry);
|
|
101
|
+
|
|
102
|
+
this.#registry['createInstance'] = this.createInstance.bind(this);
|
|
103
|
+
this.#registry['destroyInstance'] = this.destroyInstance.bind(this);
|
|
104
|
+
this.#registry['onReset'] = this.onReset.bind(this);
|
|
105
|
+
this.#registry['onInstallFinalize'] = this.onInstallFinalize.bind(this);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
93
108
|
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
export const DependencyRegistration = {
|
|
110
|
+
init(registry: typeof DependencyRegistry): void {
|
|
111
|
+
const dynamic = new $DynamicDependencyRegistry();
|
|
112
|
+
dynamic.register(registry);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RootRegistry } from '@travetto/registry';
|
|
2
|
+
|
|
3
|
+
import { DependencyRegistry } from '../src/registry';
|
|
4
|
+
|
|
5
|
+
export async function main(...[mod, cls, method, qualifier]: (string | undefined)[]): Promise<unknown> {
|
|
6
|
+
await RootRegistry.init();
|
|
7
|
+
|
|
8
|
+
const tgt = (await import(mod!))[cls!];
|
|
9
|
+
|
|
10
|
+
const inst = await DependencyRegistry.getInstance(
|
|
11
|
+
tgt,
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
13
|
+
qualifier ? Symbol.for(qualifier) : qualifier as undefined
|
|
14
|
+
);
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
16
|
+
return await (inst as Record<string, () => Promise<unknown>>)[method!]();
|
|
17
|
+
}
|
|
@@ -2,7 +2,7 @@ import { Class, ClassInstance } from '@travetto/base';
|
|
|
2
2
|
import { RootRegistry } from '@travetto/registry';
|
|
3
3
|
import { SuiteRegistry } from '@travetto/test';
|
|
4
4
|
|
|
5
|
-
import { DependencyRegistry } from '
|
|
5
|
+
import { DependencyRegistry } from '../../src/registry';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Registers a suite as injectable
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ts from 'typescript';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
TransformerState, DecoratorMeta, OnClass, OnProperty, OnStaticMethod, DecoratorUtil, LiteralUtil,
|
|
4
|
+
TransformerState, DecoratorMeta, OnClass, OnProperty, OnStaticMethod, DecoratorUtil, LiteralUtil, OnSetter
|
|
5
5
|
} from '@travetto/transformer';
|
|
6
6
|
|
|
7
7
|
const INJECTABLE_MOD = '@travetto/di/src/decorator';
|
|
@@ -11,8 +11,6 @@ const INJECTABLE_MOD = '@travetto/di/src/decorator';
|
|
|
11
11
|
*/
|
|
12
12
|
export class InjectableTransformer {
|
|
13
13
|
|
|
14
|
-
static [TransformerId] = '@trv:di';
|
|
15
|
-
|
|
16
14
|
/**
|
|
17
15
|
* Handle a specific declaration param/property
|
|
18
16
|
*/
|
|
@@ -27,15 +25,14 @@ export class InjectableTransformer {
|
|
|
27
25
|
const callExpr = existing?.expression as ts.CallExpression;
|
|
28
26
|
const args: ts.Expression[] = [...(callExpr?.arguments ?? [])];
|
|
29
27
|
|
|
30
|
-
let optional = undefined;
|
|
28
|
+
let optional: ts.Expression | undefined = undefined;
|
|
31
29
|
if (optional === undefined && !!param.questionToken) {
|
|
32
30
|
optional = state.fromLiteral(true);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}));
|
|
33
|
+
const keyParam = ts.isSetAccessorDeclaration(param) ? param.parameters[0] : param;
|
|
34
|
+
const target = state.getOrImport(state.resolveExternalType(keyParam));
|
|
35
|
+
args.unshift(state.fromLiteral({ target, optional }));
|
|
39
36
|
|
|
40
37
|
return args;
|
|
41
38
|
}
|
|
@@ -108,12 +105,14 @@ export class InjectableTransformer {
|
|
|
108
105
|
static registerInjectSetter(state: TransformerState, node: ts.SetAccessorDeclaration, dm?: DecoratorMeta): typeof node {
|
|
109
106
|
const decl = state.findDecorator(this, node, 'Inject', INJECTABLE_MOD);
|
|
110
107
|
|
|
108
|
+
const modifiers = DecoratorUtil.spliceDecorators(node, decl, [
|
|
109
|
+
state.createDecorator(INJECTABLE_MOD, 'Inject', ...this.processDeclaration(state, node)),
|
|
110
|
+
], 0);
|
|
111
|
+
|
|
111
112
|
// Doing decls
|
|
112
113
|
return state.factory.updateSetAccessorDeclaration(
|
|
113
114
|
node,
|
|
114
|
-
|
|
115
|
-
state.createDecorator(INJECTABLE_MOD, 'Inject', ...this.processDeclaration(state, node)),
|
|
116
|
-
], 0),
|
|
115
|
+
modifiers,
|
|
117
116
|
node.name,
|
|
118
117
|
node.parameters,
|
|
119
118
|
node.body
|
package/support/invoke.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { EnvInit } from '@travetto/base/bin/init';
|
|
2
|
-
|
|
3
|
-
export async function invoke(...[mod, cls, method, qualifier]: (string | undefined)[]): Promise<unknown> {
|
|
4
|
-
EnvInit.init();
|
|
5
|
-
await (await import('@travetto/base')).PhaseManager.run('init');
|
|
6
|
-
const inst = await (await import('../src/registry')).DependencyRegistry
|
|
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
|
|
13
|
-
return await (inst as Record<string, () => Promise<unknown>>)[method!]();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
invoke(...process.argv.slice(2));
|