@travetto/di 7.0.0-rc.0 → 7.0.0-rc.1
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 +1 -1
- package/package.json +3 -3
- package/src/registry/registry-index.ts +41 -50
- package/src/registry/registry-resolver.ts +29 -36
package/README.md
CHANGED
|
@@ -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.1",
|
|
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.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/transformer": "^7.0.0-rc.
|
|
33
|
+
"@travetto/transformer": "^7.0.0-rc.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@travetto/transformer": {
|
|
@@ -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
|
}
|
|
@@ -62,25 +60,25 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
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,36 +88,30 @@ 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
|
}
|
|
@@ -149,12 +141,11 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
149
141
|
|
|
150
142
|
process(events: ChangeEvent<Class>[]): void {
|
|
151
143
|
for (const ev of events) {
|
|
152
|
-
if (
|
|
153
|
-
this.#addClass(ev.curr);
|
|
154
|
-
} else if (ev.type === 'removing') {
|
|
144
|
+
if ('prev' in ev) {
|
|
155
145
|
this.#removeClass(ev.prev);
|
|
156
|
-
}
|
|
157
|
-
|
|
146
|
+
}
|
|
147
|
+
if ('curr' in ev) {
|
|
148
|
+
this.#addClass(ev.curr, 'prev' in ev);
|
|
158
149
|
}
|
|
159
150
|
}
|
|
160
151
|
}
|
|
@@ -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,7 +182,7 @@ 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
188
|
.filter(([k, input]) => readMetadata(input) !== undefined && (input.access !== 'readonly' && instance[castKey(k)] === undefined))
|
|
@@ -242,26 +233,26 @@ 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
253
|
} catch (err) {
|
|
263
254
|
// Clear it out, don't save failed constructions
|
|
264
|
-
this.#instancePromises.get(
|
|
255
|
+
this.#instancePromises.get(target)!.delete(qualifier);
|
|
265
256
|
throw err;
|
|
266
257
|
}
|
|
267
258
|
}
|
|
@@ -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,10 +4,9 @@ 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<
|
|
9
|
+
function setInMap<T>(map: Map<Class, Map<typeof key, T>>, src: Class, key: symbol | string, dest: T): void {
|
|
11
10
|
if (!map.has(src)) {
|
|
12
11
|
map.set(src, new Map());
|
|
13
12
|
}
|
|
@@ -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
|
|
|
@@ -65,13 +64,11 @@ export class DependencyRegistryResolver {
|
|
|
65
64
|
/**
|
|
66
65
|
* Register a class with the dependency resolver
|
|
67
66
|
*/
|
|
68
|
-
registerClass(config: InjectableCandidate,
|
|
67
|
+
registerClass(config: InjectableCandidate, baseParent?: Class): void {
|
|
69
68
|
const candidateType = config.candidateType;
|
|
70
|
-
const candidateClassId = candidateType.Ⲑid;
|
|
71
69
|
const target = config.target ?? candidateType;
|
|
72
70
|
|
|
73
|
-
const
|
|
74
|
-
const isSelfTarget = candidateClassId === targetClassId;
|
|
71
|
+
const isSelfTarget = target === candidateType;
|
|
75
72
|
const qualifier = config.qualifier ?? getDefaultQualifier(candidateType);
|
|
76
73
|
|
|
77
74
|
// Record qualifier if its the default for the class
|
|
@@ -80,42 +77,41 @@ export class DependencyRegistryResolver {
|
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
// Register inbound config by method and class
|
|
83
|
-
setInMap(this.#byContainerType, config.class
|
|
80
|
+
setInMap(this.#byContainerType, config.class, config.method, config);
|
|
84
81
|
|
|
85
|
-
setInMap(this.#byCandidateType,
|
|
86
|
-
setInMap(this.#byCandidateType,
|
|
82
|
+
setInMap(this.#byCandidateType, target, qualifier, config);
|
|
83
|
+
setInMap(this.#byCandidateType, candidateType, qualifier, config);
|
|
87
84
|
|
|
88
85
|
// Track interface aliases as targets
|
|
89
86
|
const interfaces = SchemaRegistryIndex.has(candidateType) ?
|
|
90
87
|
SchemaRegistryIndex.get(candidateType).get().interfaces : [];
|
|
91
88
|
|
|
92
|
-
for (const
|
|
93
|
-
setInMap(this.#byCandidateType,
|
|
89
|
+
for (const type of interfaces) {
|
|
90
|
+
setInMap(this.#byCandidateType, type, qualifier, config);
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
// If targeting self (default @Injectable behavior)
|
|
97
|
-
if (isSelfTarget &&
|
|
98
|
-
setInMap(this.#byCandidateType,
|
|
94
|
+
if (isSelfTarget && baseParent) {
|
|
95
|
+
setInMap(this.#byCandidateType, baseParent, qualifier, config);
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
// Registry primary candidates
|
|
102
99
|
if (config.primary) {
|
|
103
|
-
if (
|
|
104
|
-
setInMap(this.#byCandidateType,
|
|
100
|
+
if (baseParent) {
|
|
101
|
+
setInMap(this.#byCandidateType, baseParent, PrimaryCandidateSymbol, config);
|
|
105
102
|
}
|
|
106
103
|
|
|
107
104
|
// Register primary for self
|
|
108
|
-
setInMap(this.#byCandidateType,
|
|
105
|
+
setInMap(this.#byCandidateType, target, PrimaryCandidateSymbol, config);
|
|
109
106
|
|
|
110
107
|
// Register primary if only one interface provided and no parent config
|
|
111
|
-
if (interfaces.length === 1 && (!
|
|
108
|
+
if (interfaces.length === 1 && (!baseParent || !this.#byContainerType.has(baseParent))) {
|
|
112
109
|
const [primaryInterface] = interfaces;
|
|
113
|
-
|
|
114
|
-
setInMap(this.#byCandidateType, primaryClassId, PrimaryCandidateSymbol, config);
|
|
110
|
+
setInMap(this.#byCandidateType, primaryInterface, PrimaryCandidateSymbol, config);
|
|
115
111
|
} else if (isSelfTarget) {
|
|
116
112
|
// Register primary for all interfaces if self targeting
|
|
117
|
-
for (const
|
|
118
|
-
setInMap(this.#byCandidateType,
|
|
113
|
+
for (const type of interfaces) {
|
|
114
|
+
setInMap(this.#byCandidateType, type, PrimaryCandidateSymbol, config);
|
|
119
115
|
}
|
|
120
116
|
}
|
|
121
117
|
}
|
|
@@ -127,7 +123,7 @@ export class DependencyRegistryResolver {
|
|
|
127
123
|
* @param qualifier
|
|
128
124
|
*/
|
|
129
125
|
resolveCandidate<T>(candidateType: Class<T>, qualifier?: symbol, resolution?: ResolutionType): Resolved<T> {
|
|
130
|
-
const qualifiers = this.#byCandidateType.get(candidateType
|
|
126
|
+
const qualifiers = this.#byCandidateType.get(candidateType) ?? new Map<symbol, InjectableCandidate>();
|
|
131
127
|
|
|
132
128
|
let config: InjectableCandidate;
|
|
133
129
|
|
|
@@ -152,24 +148,21 @@ export class DependencyRegistryResolver {
|
|
|
152
148
|
return {
|
|
153
149
|
candidate: castTo(config),
|
|
154
150
|
qualifier,
|
|
155
|
-
|
|
151
|
+
target: (config.target ?? config.candidateType)
|
|
156
152
|
};
|
|
157
153
|
}
|
|
158
154
|
|
|
159
155
|
removeClass(cls: Class, qualifier: symbol): void {
|
|
160
|
-
const classId = cls.Ⲑid;
|
|
161
156
|
this.#defaultSymbols.delete(qualifier);
|
|
162
|
-
this.#byCandidateType.get(
|
|
163
|
-
this.#byContainerType.get(
|
|
157
|
+
this.#byCandidateType.get(cls)!.delete(qualifier);
|
|
158
|
+
this.#byContainerType.get(cls)!.delete(qualifier);
|
|
164
159
|
}
|
|
165
160
|
|
|
166
161
|
getCandidateEntries(candidateType: Class): [symbol, InjectableCandidate][] {
|
|
167
|
-
|
|
168
|
-
return [...this.#byCandidateType.get(candidateTypeId)?.entries() ?? []];
|
|
162
|
+
return [...this.#byCandidateType.get(candidateType)?.entries() ?? []];
|
|
169
163
|
}
|
|
170
164
|
|
|
171
165
|
getContainerEntries(containerType: Class): [symbol, InjectableCandidate][] {
|
|
172
|
-
|
|
173
|
-
return [...this.#byContainerType.get(containerTypeId)?.entries() ?? []];
|
|
166
|
+
return [...this.#byContainerType.get(containerType)?.entries() ?? []];
|
|
174
167
|
}
|
|
175
168
|
}
|