@travetto/di 7.0.0-rc.0 → 7.0.0-rc.2
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 +6 -6
- package/package.json +3 -3
- package/src/decorator.ts +12 -12
- package/src/registry/registry-adapter.ts +15 -16
- package/src/registry/registry-index.ts +67 -76
- package/src/registry/registry-resolver.ts +37 -42
- package/src/types.ts +3 -3
package/README.md
CHANGED
|
@@ -135,7 +135,7 @@ The [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decor
|
|
|
135
135
|
**Code: Example Injectable with dependencies as Inject fields**
|
|
136
136
|
```typescript
|
|
137
137
|
import { Injectable, Inject } from '@travetto/di';
|
|
138
|
-
import { DependentService } from './
|
|
138
|
+
import { DependentService } from './dependency.ts';
|
|
139
139
|
|
|
140
140
|
@Injectable()
|
|
141
141
|
class CustomService {
|
|
@@ -153,15 +153,15 @@ The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/d
|
|
|
153
153
|
**Code: Example Injectable with dependencies in constructor**
|
|
154
154
|
```typescript
|
|
155
155
|
import { Injectable } from '@travetto/di';
|
|
156
|
-
import { DependentService } from './
|
|
156
|
+
import { DependentService } from './dependency.ts';
|
|
157
157
|
|
|
158
158
|
@Injectable()
|
|
159
159
|
class CustomService {
|
|
160
160
|
|
|
161
161
|
dependentService: DependentService;
|
|
162
162
|
|
|
163
|
-
constructor(
|
|
164
|
-
this.dependentService =
|
|
163
|
+
constructor(service: DependentService) {
|
|
164
|
+
this.dependentService = service;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
async coolOperation() {
|
|
@@ -176,7 +176,7 @@ Via [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/d
|
|
|
176
176
|
```typescript
|
|
177
177
|
import { InjectableFactory } from '@travetto/di';
|
|
178
178
|
|
|
179
|
-
import { DependentService, CustomService } from './
|
|
179
|
+
import { DependentService, CustomService } from './dependency.ts';
|
|
180
180
|
|
|
181
181
|
class Config {
|
|
182
182
|
@InjectableFactory()
|
|
@@ -268,7 +268,7 @@ class Service {
|
|
|
268
268
|
```
|
|
269
269
|
|
|
270
270
|
## Manual Invocation
|
|
271
|
-
Some times you will need to lookup a dependency dynamically, or you want to control the injection process at a more granular level. To achieve that you will need to directly access the [DependencyRegistryIndex](https://github.com/travetto/travetto/tree/main/module/di/src/registry/registry-index.ts#
|
|
271
|
+
Some times you will need to lookup a dependency dynamically, or you want to control the injection process at a more granular level. To achieve that you will need to directly access the [DependencyRegistryIndex](https://github.com/travetto/travetto/tree/main/module/di/src/registry/registry-index.ts#L19). The registry allows for requesting a dependency by class reference:
|
|
272
272
|
|
|
273
273
|
**Code: Example of Manual Lookup**
|
|
274
274
|
```typescript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/di",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.2",
|
|
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": "^7.0.0-rc.
|
|
30
|
+
"@travetto/registry": "^7.0.0-rc.2"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/transformer": "^7.0.0-rc.
|
|
33
|
+
"@travetto/transformer": "^7.0.0-rc.2"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@travetto/transformer": {
|
package/src/decorator.ts
CHANGED
|
@@ -4,17 +4,17 @@ import { CONSTRUCTOR_PROPERTY } from '@travetto/schema';
|
|
|
4
4
|
import { InjectableCandidate, ResolutionType } from './types.ts';
|
|
5
5
|
import { DependencyRegistryIndex } from './registry/registry-index.ts';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
typeof
|
|
7
|
+
const fromInput = <T extends { qualifier?: symbol }>(input?: T | symbol): T =>
|
|
8
|
+
typeof input === 'symbol' ? castTo({ qualifier: input }) : (input ?? castTo<T>({}));
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Indicate that a class is able to be injected
|
|
12
12
|
* @augments `@travetto/schema:Schema`
|
|
13
13
|
* @kind decorator
|
|
14
14
|
*/
|
|
15
|
-
export function Injectable(
|
|
15
|
+
export function Injectable(input?: Partial<InjectableCandidate> | symbol) {
|
|
16
16
|
return <T extends Class>(cls: T): void => {
|
|
17
|
-
DependencyRegistryIndex.getForRegister(cls).registerClass(
|
|
17
|
+
DependencyRegistryIndex.getForRegister(cls).registerClass(fromInput(input));
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -26,15 +26,15 @@ export type InjectConfig = { qualifier?: symbol, resolution?: ResolutionType };
|
|
|
26
26
|
* @augments `@travetto/schema:Input`
|
|
27
27
|
* @kind decorator
|
|
28
28
|
*/
|
|
29
|
-
export function Inject(
|
|
30
|
-
return (instanceOrCls: Class | ClassInstance, property?: string
|
|
31
|
-
const
|
|
29
|
+
export function Inject(input?: InjectConfig | symbol) {
|
|
30
|
+
return (instanceOrCls: Class | ClassInstance, property?: string, idx?: number | PropertyDescriptor): void => {
|
|
31
|
+
const config = fromInput(input);
|
|
32
32
|
const cls = getClass(instanceOrCls);
|
|
33
33
|
const propertyKey = property ?? CONSTRUCTOR_PROPERTY;
|
|
34
34
|
if (typeof idx !== 'number') {
|
|
35
|
-
DependencyRegistryIndex.registerFieldMetadata(cls, propertyKey,
|
|
35
|
+
DependencyRegistryIndex.registerFieldMetadata(cls, propertyKey, config);
|
|
36
36
|
} else {
|
|
37
|
-
DependencyRegistryIndex.registerParameterMetadata(cls, propertyKey, idx,
|
|
37
|
+
DependencyRegistryIndex.registerParameterMetadata(cls, propertyKey, idx, config);
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
}
|
|
@@ -44,9 +44,9 @@ export function Inject(config?: InjectConfig | symbol) {
|
|
|
44
44
|
* @augments `@travetto/schema:Method`
|
|
45
45
|
* @kind decorator
|
|
46
46
|
*/
|
|
47
|
-
export function InjectableFactory(
|
|
48
|
-
return <T extends Class>(cls: T, property: string
|
|
49
|
-
DependencyRegistryIndex.getForRegister(cls).registerFactory(property,
|
|
47
|
+
export function InjectableFactory(input?: Partial<InjectableCandidate> | symbol) {
|
|
48
|
+
return <T extends Class>(cls: T, property: string, descriptor: TypedPropertyDescriptor<(...args: Any[]) => Any>): void => {
|
|
49
|
+
DependencyRegistryIndex.getForRegister(cls).registerFactory(property, fromInput(input), {
|
|
50
50
|
factory: (...params: unknown[]) => descriptor.value!.apply(cls, params),
|
|
51
51
|
});
|
|
52
52
|
};
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { RegistryAdapter } from '@travetto/registry';
|
|
2
|
-
import { Class, classConstruct, describeFunction,
|
|
2
|
+
import { Class, classConstruct, describeFunction, safeAssign } from '@travetto/runtime';
|
|
3
3
|
import { CONSTRUCTOR_PROPERTY, SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import { InjectableConfig, getDefaultQualifier, InjectableCandidate } from '../types';
|
|
6
6
|
|
|
7
|
-
function combineInjectableCandidates<T extends InjectableCandidate>(base: T, ...
|
|
8
|
-
for (const
|
|
9
|
-
safeAssign(base,
|
|
7
|
+
function combineInjectableCandidates<T extends InjectableCandidate>(base: T, ...overrides: Partial<T>[]): typeof base {
|
|
8
|
+
for (const override of overrides) {
|
|
9
|
+
safeAssign(base, override);
|
|
10
10
|
}
|
|
11
11
|
return base;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function combineClasses<T extends InjectableConfig>(base: T, ...
|
|
15
|
-
for (const
|
|
14
|
+
function combineClasses<T extends InjectableConfig>(base: T, ...overrides: Partial<T>[]): typeof base {
|
|
15
|
+
for (const override of overrides) {
|
|
16
16
|
Object.assign(base, {
|
|
17
17
|
...base,
|
|
18
|
-
...
|
|
18
|
+
...override,
|
|
19
19
|
candidates: {
|
|
20
20
|
...base.candidates,
|
|
21
|
-
...
|
|
21
|
+
...override.candidates,
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
}
|
|
@@ -38,7 +38,7 @@ export class DependencyRegistryAdapter implements RegistryAdapter<InjectableConf
|
|
|
38
38
|
return combineClasses(this.#config, ...data);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
registerFactory(method: string
|
|
41
|
+
registerFactory(method: string, ...data: Partial<InjectableCandidate<unknown>>[]): InjectableCandidate {
|
|
42
42
|
const { candidates } = this.register();
|
|
43
43
|
candidates[method] ??= {
|
|
44
44
|
class: this.#cls,
|
|
@@ -62,17 +62,16 @@ export class DependencyRegistryAdapter implements RegistryAdapter<InjectableConf
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
finalize(): void {
|
|
65
|
-
for (const
|
|
66
|
-
const
|
|
67
|
-
const candidateType = SchemaRegistryIndex.get(
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
for (const method of Object.keys(this.#config.candidates)) {
|
|
66
|
+
const candidate = this.#config.candidates[method];
|
|
67
|
+
const candidateType = SchemaRegistryIndex.get(candidate.class).getMethodReturnType(method);
|
|
68
|
+
candidate.candidateType = candidateType;
|
|
69
|
+
candidate.qualifier ??= getDefaultQualifier(candidateType);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
getCandidateConfigs(): InjectableCandidate[] {
|
|
74
|
-
|
|
75
|
-
return entries
|
|
74
|
+
return Object.values(this.#config.candidates)
|
|
76
75
|
.filter(item => (item.enabled ?? true) === true || (typeof item.enabled === 'function' && item.enabled()))
|
|
77
76
|
.filter(item => item.method !== CONSTRUCTOR_PROPERTY || !describeFunction(item.candidateType)?.abstract);
|
|
78
77
|
}
|
|
@@ -9,11 +9,9 @@ import { DependencyRegistryResolver } from './registry-resolver';
|
|
|
9
9
|
|
|
10
10
|
const MetadataSymbol = Symbol();
|
|
11
11
|
|
|
12
|
-
type ClassId = string;
|
|
13
12
|
const hasPostConstruct = hasFunction<{ postConstruct: () => Promise<unknown> }>('postConstruct');
|
|
14
13
|
const hasPreDestroy = hasFunction<{ preDestroy: () => Promise<unknown> }>('preDestroy');
|
|
15
14
|
|
|
16
|
-
|
|
17
15
|
function readMetadata(item: { metadata?: Record<symbol, unknown> }): Dependency | undefined {
|
|
18
16
|
return castTo<Dependency | undefined>(item.metadata?.[MetadataSymbol]);
|
|
19
17
|
}
|
|
@@ -35,15 +33,15 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
static getCandidateTypes<T>(candidateType: Class<T>): Class<T>[] {
|
|
38
|
-
return this.#instance.getCandidates(candidateType).map(
|
|
36
|
+
return this.#instance.getCandidates(candidateType).map(candidate => candidate.candidateType);
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
static getInstances<T>(candidateType: Class<T>, predicate?: (
|
|
39
|
+
static getInstances<T>(candidateType: Class<T>, predicate?: (config: InjectableCandidate<T>) => boolean): Promise<T[]> {
|
|
42
40
|
return this.#instance.getInstances<T>(candidateType, predicate);
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
static injectFields<T extends { constructor: Class<T> }>(
|
|
46
|
-
return this.#instance.injectFields(cls,
|
|
43
|
+
static injectFields<T extends { constructor: Class<T> }>(item: T, cls = item.constructor): Promise<T> {
|
|
44
|
+
return this.#instance.injectFields(cls, item, cls);
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
static getOptional(cls: Class): InjectableConfig | undefined {
|
|
@@ -54,33 +52,33 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
54
52
|
SchemaRegistryIndex.getForRegister(cls).registerMetadata<InjectableClassMetadata>(MetadataSymbol, metadata);
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
static registerParameterMetadata(cls: Class, method: string
|
|
55
|
+
static registerParameterMetadata(cls: Class, method: string, index: number, metadata: Dependency): void {
|
|
58
56
|
SchemaRegistryIndex.getForRegister(cls).registerParameterMetadata(method, index, MetadataSymbol, metadata);
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
static registerFieldMetadata(cls: Class, field: string
|
|
59
|
+
static registerFieldMetadata(cls: Class, field: string, metadata: Dependency): void {
|
|
62
60
|
SchemaRegistryIndex.getForRegister(cls).registerFieldMetadata(field, MetadataSymbol, metadata);
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
63
|
+
#proxies = new Map<string, Map<symbol | undefined, RetargettingProxy<unknown>>>();
|
|
64
|
+
#instances = new Map<Class, Map<symbol, unknown>>();
|
|
65
|
+
#instancePromises = new Map<Class, Map<symbol, Promise<unknown>>>();
|
|
68
66
|
#resolver = new DependencyRegistryResolver();
|
|
69
67
|
|
|
70
68
|
#proxyInstance<T>(target: Class<unknown>, qualifier: symbol, instance: T): T {
|
|
71
|
-
const classId = target.Ⲑid;
|
|
72
69
|
let proxy: RetargettingProxy<unknown>;
|
|
70
|
+
const targetId = target.Ⲑid;
|
|
73
71
|
|
|
74
|
-
if (!this.#proxies.has(
|
|
75
|
-
this.#proxies.set(
|
|
72
|
+
if (!this.#proxies.has(targetId)) {
|
|
73
|
+
this.#proxies.set(targetId, new Map());
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
if (!this.#proxies.get(
|
|
76
|
+
if (!this.#proxies.get(targetId)!.has(qualifier)) {
|
|
79
77
|
proxy = new RetargettingProxy(instance);
|
|
80
|
-
this.#proxies.get(
|
|
78
|
+
this.#proxies.get(targetId)!.set(qualifier, proxy);
|
|
81
79
|
console.debug('Registering proxy', { id: target.Ⲑid, qualifier: qualifier.toString() });
|
|
82
80
|
} else {
|
|
83
|
-
proxy = this.#proxies.get(
|
|
81
|
+
proxy = this.#proxies.get(targetId)!.get(qualifier)!;
|
|
84
82
|
proxy.setTarget(instance);
|
|
85
83
|
console.debug('Updating target', {
|
|
86
84
|
id: target.Ⲑid, qualifier: qualifier.toString(), instanceType: target.name
|
|
@@ -90,53 +88,47 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
90
88
|
return proxy.get();
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
#addClass(cls: Class): void {
|
|
91
|
+
#addClass(cls: Class, forceCreate: boolean = false): void {
|
|
94
92
|
const adapter = this.store.get(cls);
|
|
95
93
|
|
|
96
94
|
for (const config of adapter.getCandidateConfigs()) {
|
|
97
95
|
const parentClass = getParentClass(config.candidateType);
|
|
98
96
|
const parentConfig = parentClass ? this.store.getOptional(parentClass) : undefined;
|
|
99
97
|
const hasParentBase = (parentConfig || (parentClass && !!describeFunction(parentClass)?.abstract));
|
|
100
|
-
const
|
|
101
|
-
this.#resolver.registerClass(config,
|
|
102
|
-
if (config.autoInject) {
|
|
98
|
+
const baseParent = hasParentBase ? parentClass : undefined;
|
|
99
|
+
this.#resolver.registerClass(config, baseParent);
|
|
100
|
+
if (config.autoInject || forceCreate) {
|
|
103
101
|
// Don't wait
|
|
104
|
-
|
|
102
|
+
Util.queueMacroTask().then(() => {
|
|
103
|
+
this.getInstance(config.candidateType, config.qualifier);
|
|
104
|
+
});
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
#changedClass(cls: Class, _prev: Class): void {
|
|
110
|
-
// Reload instances
|
|
111
|
-
for (const qualifier of this.#proxies.get(cls.Ⲑid)?.keys() ?? []) {
|
|
112
|
-
// Timing matters due to create instance being asynchronous
|
|
113
|
-
Util.queueMacroTask().then(() => { this.getInstance(cls, qualifier); });
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
109
|
#removeClass(cls: Class): void {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (this.#instances.has(classId)) {
|
|
110
|
+
if (this.#instances.has(cls)) {
|
|
121
111
|
for (const [qualifier, config] of this.#resolver.getContainerEntries(cls)) {
|
|
122
|
-
|
|
112
|
+
try {
|
|
113
|
+
this.destroyInstance(config.candidateType, qualifier);
|
|
114
|
+
} catch { }
|
|
123
115
|
}
|
|
124
116
|
}
|
|
125
117
|
}
|
|
126
118
|
|
|
127
119
|
|
|
128
|
-
async #resolveDependencyValue(dependency: Dependency, input: SchemaFieldConfig | SchemaParameterConfig,
|
|
120
|
+
async #resolveDependencyValue(dependency: Dependency, input: SchemaFieldConfig | SchemaParameterConfig, cls: Class): Promise<unknown> {
|
|
129
121
|
try {
|
|
130
122
|
const target = dependency.target ?? input.type;
|
|
131
123
|
return await this.getInstance(target, dependency.qualifier, dependency.resolution);
|
|
132
|
-
} catch (
|
|
133
|
-
if (input.required?.active === false &&
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (input.required?.active === false && error instanceof InjectionError && error.category === 'notfound') {
|
|
134
126
|
return undefined;
|
|
135
127
|
} else {
|
|
136
|
-
if (
|
|
137
|
-
|
|
128
|
+
if (error && error instanceof Error) {
|
|
129
|
+
error.message = `${error.message} via=${cls.Ⲑid}[${input.name?.toString() ?? 'constructor'}]`;
|
|
138
130
|
}
|
|
139
|
-
throw
|
|
131
|
+
throw error;
|
|
140
132
|
}
|
|
141
133
|
}
|
|
142
134
|
}
|
|
@@ -148,13 +140,12 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
148
140
|
}
|
|
149
141
|
|
|
150
142
|
process(events: ChangeEvent<Class>[]): void {
|
|
151
|
-
for (const
|
|
152
|
-
if (
|
|
153
|
-
this.#
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.#changedClass(ev.curr, ev.prev);
|
|
143
|
+
for (const event of events) {
|
|
144
|
+
if ('previous' in event) {
|
|
145
|
+
this.#removeClass(event.previous);
|
|
146
|
+
}
|
|
147
|
+
if ('current' in event) {
|
|
148
|
+
this.#addClass(event.current, 'previous' in event);
|
|
158
149
|
}
|
|
159
150
|
}
|
|
160
151
|
}
|
|
@@ -163,15 +154,15 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
163
154
|
* Get all available candidates for a given type
|
|
164
155
|
*/
|
|
165
156
|
getCandidates<T>(candidateType: Class<T>): InjectableCandidate<T>[] {
|
|
166
|
-
return this.#resolver.getCandidateEntries(candidateType).map(([_,
|
|
157
|
+
return this.#resolver.getCandidateEntries(candidateType).map(([_, candidate]) => castTo<InjectableCandidate<T>>(candidate));
|
|
167
158
|
}
|
|
168
159
|
|
|
169
160
|
/**
|
|
170
161
|
* Get candidate instances by target type, with an optional filter
|
|
171
162
|
*/
|
|
172
|
-
getInstances<T>(candidateType: Class<T>, predicate?: (
|
|
173
|
-
const inputs = this.getCandidates<T>(candidateType).filter(
|
|
174
|
-
return Promise.all(inputs.map(
|
|
163
|
+
getInstances<T>(candidateType: Class<T>, predicate?: (config: InjectableCandidate<T>) => boolean): Promise<T[]> {
|
|
164
|
+
const inputs = this.getCandidates<T>(candidateType).filter(candidate => !predicate || predicate(candidate));
|
|
165
|
+
return Promise.all(inputs.map(candidate => this.getInstance<T>(candidate.class, candidate.qualifier)));
|
|
175
166
|
}
|
|
176
167
|
|
|
177
168
|
/**
|
|
@@ -179,7 +170,7 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
179
170
|
*/
|
|
180
171
|
async fetchDependencyParameters<T>(candidate: InjectableCandidate<T>): Promise<unknown[]> {
|
|
181
172
|
const inputs = SchemaRegistryIndex.has(candidate.class) ?
|
|
182
|
-
SchemaRegistryIndex.
|
|
173
|
+
SchemaRegistryIndex.get(candidate.class).getMethod(candidate.method).parameters : [];
|
|
183
174
|
|
|
184
175
|
const promises = inputs
|
|
185
176
|
.map(input => this.#resolveDependencyValue(readMetadata(input) ?? {}, input, candidate.class));
|
|
@@ -191,16 +182,16 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
191
182
|
* Retrieve mapped dependencies
|
|
192
183
|
*/
|
|
193
184
|
async injectFields<T>(candidateType: Class, instance: T, srcClass: Class): Promise<T> {
|
|
194
|
-
const inputs = SchemaRegistryIndex.
|
|
185
|
+
const inputs = SchemaRegistryIndex.getOptional(candidateType)?.getFields() ?? {};
|
|
195
186
|
|
|
196
187
|
const promises = TypedObject.entries(inputs)
|
|
197
|
-
.filter(([
|
|
198
|
-
.map(async ([
|
|
188
|
+
.filter(([key, input]) => readMetadata(input) !== undefined && (input.access !== 'readonly' && instance[castKey(key)] === undefined))
|
|
189
|
+
.map(async ([key, input]) => [key, await this.#resolveDependencyValue(readMetadata(input) ?? {}, input, srcClass)] as const);
|
|
199
190
|
|
|
200
191
|
const pairs = await Promise.all(promises);
|
|
201
192
|
|
|
202
|
-
for (const [
|
|
203
|
-
instance[castKey(
|
|
193
|
+
for (const [key, value] of pairs) {
|
|
194
|
+
instance[castKey(key)] = castTo(value);
|
|
204
195
|
}
|
|
205
196
|
return instance;
|
|
206
197
|
}
|
|
@@ -226,8 +217,8 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
226
217
|
SchemaRegistryIndex.get(targetType).getMetadata<InjectableClassMetadata>(MetadataSymbol) : undefined;
|
|
227
218
|
|
|
228
219
|
// Run post constructors
|
|
229
|
-
for (const
|
|
230
|
-
await
|
|
220
|
+
for (const operation of Object.values(metadata?.postConstruct ?? {})) {
|
|
221
|
+
await operation(inst);
|
|
231
222
|
}
|
|
232
223
|
|
|
233
224
|
// Proxy if necessary
|
|
@@ -242,27 +233,27 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
242
233
|
throw new AppError('Unable to get instance when target is undefined');
|
|
243
234
|
}
|
|
244
235
|
|
|
245
|
-
const {
|
|
236
|
+
const { target, qualifier } = this.#resolver.resolveCandidate(candidateType, requestedQualifier, resolution);
|
|
246
237
|
|
|
247
|
-
if (!this.#instances.has(
|
|
248
|
-
this.#instances.set(
|
|
249
|
-
this.#instancePromises.set(
|
|
238
|
+
if (!this.#instances.has(target)) {
|
|
239
|
+
this.#instances.set(target, new Map());
|
|
240
|
+
this.#instancePromises.set(target, new Map());
|
|
250
241
|
}
|
|
251
242
|
|
|
252
|
-
if (this.#instancePromises.get(
|
|
253
|
-
return castTo(this.#instancePromises.get(
|
|
243
|
+
if (this.#instancePromises.get(target)!.has(qualifier)) {
|
|
244
|
+
return castTo(this.#instancePromises.get(target)!.get(qualifier));
|
|
254
245
|
}
|
|
255
246
|
|
|
256
247
|
const instancePromise = this.construct(candidateType, qualifier);
|
|
257
|
-
this.#instancePromises.get(
|
|
248
|
+
this.#instancePromises.get(target)!.set(qualifier, instancePromise);
|
|
258
249
|
try {
|
|
259
250
|
const instance = await instancePromise;
|
|
260
|
-
this.#instances.get(
|
|
251
|
+
this.#instances.get(target)!.set(qualifier, instance);
|
|
261
252
|
return instance;
|
|
262
|
-
} catch (
|
|
253
|
+
} catch (error) {
|
|
263
254
|
// Clear it out, don't save failed constructions
|
|
264
|
-
this.#instancePromises.get(
|
|
265
|
-
throw
|
|
255
|
+
this.#instancePromises.get(target)!.delete(qualifier);
|
|
256
|
+
throw error;
|
|
266
257
|
}
|
|
267
258
|
}
|
|
268
259
|
|
|
@@ -270,19 +261,19 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
270
261
|
* Destroy an instance
|
|
271
262
|
*/
|
|
272
263
|
destroyInstance(candidateType: Class, requestedQualifier: symbol): void {
|
|
273
|
-
const {
|
|
264
|
+
const { target, qualifier } = this.#resolver.resolveCandidate(candidateType, requestedQualifier);
|
|
274
265
|
|
|
275
|
-
const activeInstance = this.#instances.get(
|
|
266
|
+
const activeInstance = this.#instances.get(target)?.get(qualifier);
|
|
276
267
|
if (hasPreDestroy(activeInstance)) {
|
|
277
268
|
activeInstance.preDestroy();
|
|
278
269
|
}
|
|
279
270
|
|
|
280
271
|
this.#resolver.removeClass(candidateType, qualifier);
|
|
281
|
-
this.#instances.get(
|
|
282
|
-
this.#instancePromises.get(
|
|
272
|
+
this.#instances.get(target)?.delete(qualifier);
|
|
273
|
+
this.#instancePromises.get(target)?.delete(qualifier);
|
|
283
274
|
|
|
284
275
|
// May not exist
|
|
285
|
-
this.#proxies.get(
|
|
286
|
-
console.debug('On uninstall', { id:
|
|
276
|
+
this.#proxies.get(target.Ⲑid)?.get(qualifier)?.setTarget(null);
|
|
277
|
+
console.debug('On uninstall', { id: target, qualifier: qualifier.toString(), classId: target });
|
|
287
278
|
}
|
|
288
279
|
}
|
|
@@ -4,14 +4,13 @@ import { castTo, Class } from '@travetto/runtime';
|
|
|
4
4
|
import { getDefaultQualifier, InjectableCandidate, PrimaryCandidateSymbol, ResolutionType } from '../types';
|
|
5
5
|
import { InjectionError } from '../error';
|
|
6
6
|
|
|
7
|
-
type Resolved<T> = { candidate: InjectableCandidate<T>, qualifier: symbol,
|
|
8
|
-
type ClassId = string;
|
|
7
|
+
type Resolved<T> = { candidate: InjectableCandidate<T>, qualifier: symbol, target: Class };
|
|
9
8
|
|
|
10
|
-
function setInMap<T>(map: Map<
|
|
11
|
-
if (!map.has(
|
|
12
|
-
map.set(
|
|
9
|
+
function setInMap<T>(map: Map<Class, Map<typeof key, T>>, cls: Class, key: symbol | string, dest: T): void {
|
|
10
|
+
if (!map.has(cls)) {
|
|
11
|
+
map.set(cls, new Map());
|
|
13
12
|
}
|
|
14
|
-
map.get(
|
|
13
|
+
map.get(cls)!.set(key, dest);
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export class DependencyRegistryResolver {
|
|
@@ -21,17 +20,17 @@ export class DependencyRegistryResolver {
|
|
|
21
20
|
#defaultSymbols = new Set<symbol>();
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
|
-
* Maps from the requested type
|
|
23
|
+
* Maps from the requested type to the candidates
|
|
25
24
|
*/
|
|
26
|
-
#byCandidateType = new Map<
|
|
25
|
+
#byCandidateType = new Map<Class, Map<symbol, InjectableCandidate>>();
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
* Maps from inbound class
|
|
28
|
+
* Maps from inbound class file) to the candidates
|
|
30
29
|
*/
|
|
31
|
-
#byContainerType = new Map<
|
|
30
|
+
#byContainerType = new Map<Class, Map<symbol, InjectableCandidate>>();
|
|
32
31
|
|
|
33
32
|
#resolveQualifier<T>(type: Class<T>, resolution?: ResolutionType): symbol | undefined {
|
|
34
|
-
const qualifiers = this.#byCandidateType.get(type
|
|
33
|
+
const qualifiers = this.#byCandidateType.get(type) ?? new Map<symbol, InjectableCandidate>();
|
|
35
34
|
|
|
36
35
|
const resolved = [...qualifiers.keys()];
|
|
37
36
|
|
|
@@ -39,15 +38,17 @@ export class DependencyRegistryResolver {
|
|
|
39
38
|
if (qualifiers.has(PrimaryCandidateSymbol)) {
|
|
40
39
|
return PrimaryCandidateSymbol;
|
|
41
40
|
} else {
|
|
42
|
-
const filtered = resolved
|
|
41
|
+
const filtered = resolved
|
|
42
|
+
.filter(qualifier => !!qualifier)
|
|
43
|
+
.filter(qualifier => this.#defaultSymbols.has(qualifier));
|
|
43
44
|
// If there is only one default symbol
|
|
44
45
|
if (filtered.length === 1) {
|
|
45
46
|
return filtered[0];
|
|
46
47
|
} else if (filtered.length > 1) {
|
|
47
48
|
// If dealing with sub types, prioritize exact matches
|
|
48
49
|
const exact = this.getCandidateEntries(type)
|
|
49
|
-
.map(([_,
|
|
50
|
-
.filter(
|
|
50
|
+
.map(([_, candidate]) => candidate)
|
|
51
|
+
.filter(candidate => candidate.candidateType === type);
|
|
51
52
|
|
|
52
53
|
if (exact.length === 1) {
|
|
53
54
|
return exact[0].qualifier;
|
|
@@ -65,13 +66,11 @@ export class DependencyRegistryResolver {
|
|
|
65
66
|
/**
|
|
66
67
|
* Register a class with the dependency resolver
|
|
67
68
|
*/
|
|
68
|
-
registerClass(config: InjectableCandidate,
|
|
69
|
+
registerClass(config: InjectableCandidate, baseParent?: Class): void {
|
|
69
70
|
const candidateType = config.candidateType;
|
|
70
|
-
const candidateClassId = candidateType.Ⲑid;
|
|
71
71
|
const target = config.target ?? candidateType;
|
|
72
72
|
|
|
73
|
-
const
|
|
74
|
-
const isSelfTarget = candidateClassId === targetClassId;
|
|
73
|
+
const isSelfTarget = target === candidateType;
|
|
75
74
|
const qualifier = config.qualifier ?? getDefaultQualifier(candidateType);
|
|
76
75
|
|
|
77
76
|
// Record qualifier if its the default for the class
|
|
@@ -80,42 +79,41 @@ export class DependencyRegistryResolver {
|
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
// Register inbound config by method and class
|
|
83
|
-
setInMap(this.#byContainerType, config.class
|
|
82
|
+
setInMap(this.#byContainerType, config.class, config.method, config);
|
|
84
83
|
|
|
85
|
-
setInMap(this.#byCandidateType,
|
|
86
|
-
setInMap(this.#byCandidateType,
|
|
84
|
+
setInMap(this.#byCandidateType, target, qualifier, config);
|
|
85
|
+
setInMap(this.#byCandidateType, candidateType, qualifier, config);
|
|
87
86
|
|
|
88
87
|
// Track interface aliases as targets
|
|
89
88
|
const interfaces = SchemaRegistryIndex.has(candidateType) ?
|
|
90
89
|
SchemaRegistryIndex.get(candidateType).get().interfaces : [];
|
|
91
90
|
|
|
92
|
-
for (const
|
|
93
|
-
setInMap(this.#byCandidateType,
|
|
91
|
+
for (const type of interfaces) {
|
|
92
|
+
setInMap(this.#byCandidateType, type, qualifier, config);
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
// If targeting self (default @Injectable behavior)
|
|
97
|
-
if (isSelfTarget &&
|
|
98
|
-
setInMap(this.#byCandidateType,
|
|
96
|
+
if (isSelfTarget && baseParent) {
|
|
97
|
+
setInMap(this.#byCandidateType, baseParent, qualifier, config);
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
// Registry primary candidates
|
|
102
101
|
if (config.primary) {
|
|
103
|
-
if (
|
|
104
|
-
setInMap(this.#byCandidateType,
|
|
102
|
+
if (baseParent) {
|
|
103
|
+
setInMap(this.#byCandidateType, baseParent, PrimaryCandidateSymbol, config);
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
// Register primary for self
|
|
108
|
-
setInMap(this.#byCandidateType,
|
|
107
|
+
setInMap(this.#byCandidateType, target, PrimaryCandidateSymbol, config);
|
|
109
108
|
|
|
110
109
|
// Register primary if only one interface provided and no parent config
|
|
111
|
-
if (interfaces.length === 1 && (!
|
|
110
|
+
if (interfaces.length === 1 && (!baseParent || !this.#byContainerType.has(baseParent))) {
|
|
112
111
|
const [primaryInterface] = interfaces;
|
|
113
|
-
|
|
114
|
-
setInMap(this.#byCandidateType, primaryClassId, PrimaryCandidateSymbol, config);
|
|
112
|
+
setInMap(this.#byCandidateType, primaryInterface, PrimaryCandidateSymbol, config);
|
|
115
113
|
} else if (isSelfTarget) {
|
|
116
114
|
// Register primary for all interfaces if self targeting
|
|
117
|
-
for (const
|
|
118
|
-
setInMap(this.#byCandidateType,
|
|
115
|
+
for (const type of interfaces) {
|
|
116
|
+
setInMap(this.#byCandidateType, type, PrimaryCandidateSymbol, config);
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
}
|
|
@@ -127,7 +125,7 @@ export class DependencyRegistryResolver {
|
|
|
127
125
|
* @param qualifier
|
|
128
126
|
*/
|
|
129
127
|
resolveCandidate<T>(candidateType: Class<T>, qualifier?: symbol, resolution?: ResolutionType): Resolved<T> {
|
|
130
|
-
const qualifiers = this.#byCandidateType.get(candidateType
|
|
128
|
+
const qualifiers = this.#byCandidateType.get(candidateType) ?? new Map<symbol, InjectableCandidate>();
|
|
131
129
|
|
|
132
130
|
let config: InjectableCandidate;
|
|
133
131
|
|
|
@@ -152,24 +150,21 @@ export class DependencyRegistryResolver {
|
|
|
152
150
|
return {
|
|
153
151
|
candidate: castTo(config),
|
|
154
152
|
qualifier,
|
|
155
|
-
|
|
153
|
+
target: (config.target ?? config.candidateType)
|
|
156
154
|
};
|
|
157
155
|
}
|
|
158
156
|
|
|
159
157
|
removeClass(cls: Class, qualifier: symbol): void {
|
|
160
|
-
const classId = cls.Ⲑid;
|
|
161
158
|
this.#defaultSymbols.delete(qualifier);
|
|
162
|
-
this.#byCandidateType.get(
|
|
163
|
-
this.#byContainerType.get(
|
|
159
|
+
this.#byCandidateType.get(cls)!.delete(qualifier);
|
|
160
|
+
this.#byContainerType.get(cls)!.delete(qualifier);
|
|
164
161
|
}
|
|
165
162
|
|
|
166
163
|
getCandidateEntries(candidateType: Class): [symbol, InjectableCandidate][] {
|
|
167
|
-
|
|
168
|
-
return [...this.#byCandidateType.get(candidateTypeId)?.entries() ?? []];
|
|
164
|
+
return [...this.#byCandidateType.get(candidateType)?.entries() ?? []];
|
|
169
165
|
}
|
|
170
166
|
|
|
171
167
|
getContainerEntries(containerType: Class): [symbol, InjectableCandidate][] {
|
|
172
|
-
|
|
173
|
-
return [...this.#byContainerType.get(containerTypeId)?.entries() ?? []];
|
|
168
|
+
return [...this.#byContainerType.get(containerType)?.entries() ?? []];
|
|
174
169
|
}
|
|
175
170
|
}
|
package/src/types.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface InjectableCandidate<T = unknown> {
|
|
|
32
32
|
/**
|
|
33
33
|
* Method that is injectable on class
|
|
34
34
|
*/
|
|
35
|
-
method: string
|
|
35
|
+
method: string;
|
|
36
36
|
/**
|
|
37
37
|
* Method handle
|
|
38
38
|
*/
|
|
@@ -74,7 +74,7 @@ export interface InjectableConfig<T = unknown> {
|
|
|
74
74
|
/**
|
|
75
75
|
* Candidates that are injectable
|
|
76
76
|
*/
|
|
77
|
-
candidates: Record<string
|
|
77
|
+
candidates: Record<string, InjectableCandidate>;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export function getDefaultQualifier(cls: Class): symbol {
|
|
@@ -85,5 +85,5 @@ export function getDefaultQualifier(cls: Class): symbol {
|
|
|
85
85
|
export const PrimaryCandidateSymbol = Symbol();
|
|
86
86
|
|
|
87
87
|
export type InjectableClassMetadata = {
|
|
88
|
-
postConstruct: Record<string
|
|
88
|
+
postConstruct: Record<string, (<T>(inst: T) => Promise<void>)>;
|
|
89
89
|
};
|