@travetto/di 6.0.1 → 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 +14 -14
- package/__index__.ts +2 -1
- package/package.json +3 -3
- package/src/decorator.ts +30 -57
- package/src/error.ts +2 -3
- package/src/registry/registry-adapter.ts +79 -0
- package/src/registry/registry-index.ts +279 -0
- package/src/registry/registry-resolver.ts +168 -0
- package/src/types.ts +47 -54
- package/support/test/suite.ts +14 -13
- package/src/registry.ts +0 -579
- package/support/dynamic.injection.ts +0 -108
- package/support/transformer.injectable.ts +0 -172
package/src/registry.ts
DELETED
|
@@ -1,579 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Class, Runtime, asConstructable, castTo, classConstruct, describeFunction,
|
|
3
|
-
asFull, castKey, TypedFunction, hasFunction, AppError
|
|
4
|
-
} from '@travetto/runtime';
|
|
5
|
-
import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
|
|
6
|
-
|
|
7
|
-
import { Dependency, InjectableConfig, ClassTarget, InjectableFactoryConfig, PostConstructHandler } from './types.ts';
|
|
8
|
-
import { InjectionError } from './error.ts';
|
|
9
|
-
|
|
10
|
-
class AutoCreate { }
|
|
11
|
-
type TargetId = string;
|
|
12
|
-
type ClassId = string;
|
|
13
|
-
export type Resolved<T> = { config: InjectableConfig<T>, qualifier: symbol, id: string };
|
|
14
|
-
|
|
15
|
-
export type ResolutionType = 'strict' | 'loose' | 'any';
|
|
16
|
-
|
|
17
|
-
const PrimaryCandidateSymbol = Symbol();
|
|
18
|
-
|
|
19
|
-
const hasPostConstruct = hasFunction<{ postConstruct: () => Promise<unknown> }>('postConstruct');
|
|
20
|
-
const hasPreDestroy = hasFunction<{ preDestroy: () => Promise<unknown> }>('preDestroy');
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Dependency registry
|
|
24
|
-
*/
|
|
25
|
-
class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
26
|
-
pendingFinalize: Class[] = [];
|
|
27
|
-
|
|
28
|
-
defaultSymbols = new Set<symbol>();
|
|
29
|
-
|
|
30
|
-
instances = new Map<TargetId, Map<symbol, unknown>>();
|
|
31
|
-
instancePromises = new Map<TargetId, Map<symbol, Promise<unknown>>>();
|
|
32
|
-
|
|
33
|
-
factories = new Map<TargetId, Map<Class, InjectableConfig>>();
|
|
34
|
-
|
|
35
|
-
targetToClass = new Map<TargetId, Map<symbol, string>>();
|
|
36
|
-
classToTarget = new Map<ClassId, Map<symbol, TargetId>>();
|
|
37
|
-
|
|
38
|
-
constructor() {
|
|
39
|
-
super(RootRegistry);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Resolve the target given a qualifier
|
|
44
|
-
* @param target
|
|
45
|
-
* @param qualifier
|
|
46
|
-
*/
|
|
47
|
-
resolveTarget<T>(target: ClassTarget<T>, qualifier?: symbol, resolution?: ResolutionType): Resolved<T> {
|
|
48
|
-
const qualifiers = this.targetToClass.get(target.Ⲑid) ?? new Map<symbol, string>();
|
|
49
|
-
|
|
50
|
-
let cls: string | undefined;
|
|
51
|
-
|
|
52
|
-
if (qualifier && qualifiers.has(qualifier)) {
|
|
53
|
-
cls = qualifiers.get(qualifier);
|
|
54
|
-
} else {
|
|
55
|
-
const resolved = [...qualifiers.keys()];
|
|
56
|
-
if (!qualifier) {
|
|
57
|
-
// If primary found
|
|
58
|
-
if (qualifiers.has(PrimaryCandidateSymbol)) {
|
|
59
|
-
qualifier = PrimaryCandidateSymbol;
|
|
60
|
-
} else {
|
|
61
|
-
// If there is only one default symbol
|
|
62
|
-
const filtered = resolved.filter(x => !!x).filter(x => this.defaultSymbols.has(x));
|
|
63
|
-
if (filtered.length === 1) {
|
|
64
|
-
qualifier = filtered[0];
|
|
65
|
-
} else if (filtered.length > 1) {
|
|
66
|
-
// If dealing with sub types, prioritize exact matches
|
|
67
|
-
const exact = this
|
|
68
|
-
.getCandidateTypes(castTo<Class>(target))
|
|
69
|
-
.filter(x => x.class === target);
|
|
70
|
-
if (exact.length === 1) {
|
|
71
|
-
qualifier = exact[0].qualifier;
|
|
72
|
-
} else {
|
|
73
|
-
if (resolution === 'any') {
|
|
74
|
-
qualifier = filtered[0];
|
|
75
|
-
} else {
|
|
76
|
-
throw new InjectionError('Dependency has multiple candidates', target, filtered);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!qualifier) {
|
|
84
|
-
throw new InjectionError('Dependency not found', target);
|
|
85
|
-
} else if (!qualifiers.has(qualifier)) {
|
|
86
|
-
if (!this.defaultSymbols.has(qualifier) && resolution === 'loose') {
|
|
87
|
-
console.debug('Unable to find specific dependency, falling back to general instance', { qualifier, target: target.Ⲑid });
|
|
88
|
-
return this.resolveTarget(target);
|
|
89
|
-
}
|
|
90
|
-
throw new InjectionError('Dependency not found', target, [qualifier]);
|
|
91
|
-
} else {
|
|
92
|
-
cls = qualifiers.get(qualifier!)!;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const config: InjectableConfig<T> = castTo(this.get(cls!));
|
|
97
|
-
return {
|
|
98
|
-
qualifier,
|
|
99
|
-
config,
|
|
100
|
-
id: (config.factory ? config.target : config.class).Ⲑid
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Retrieve all dependencies
|
|
106
|
-
*/
|
|
107
|
-
async fetchDependencies<T>(managed: InjectableConfig<T>, deps?: Dependency[], keys?: string[]): Promise<unknown[]> {
|
|
108
|
-
if (!deps || !deps.length) {
|
|
109
|
-
return [];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const promises = deps.map(async (x, i) => {
|
|
113
|
-
try {
|
|
114
|
-
return await this.getInstance(x.target, x.qualifier, x.resolution);
|
|
115
|
-
} catch (err) {
|
|
116
|
-
if (x.optional && err instanceof InjectionError && err.category === 'notfound') {
|
|
117
|
-
return undefined;
|
|
118
|
-
} else {
|
|
119
|
-
if (err && err instanceof Error) {
|
|
120
|
-
err.message = `${err.message} via=${managed.class.Ⲑid}[${keys?.[i] ?? 'constructor'}]`;
|
|
121
|
-
}
|
|
122
|
-
throw err;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
return await Promise.all(promises);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Resolve all field dependencies
|
|
132
|
-
*/
|
|
133
|
-
async resolveFieldDependencies<T>(config: InjectableConfig<T>, instance: T): Promise<void> {
|
|
134
|
-
const keys = Object.keys(config.dependencies.fields ?? {})
|
|
135
|
-
.filter(k => instance[castKey<T>(k)] === undefined); // Filter out already set ones
|
|
136
|
-
|
|
137
|
-
// And auto-wire
|
|
138
|
-
if (keys.length) {
|
|
139
|
-
const deps = await this.fetchDependencies(config, keys.map(x => config.dependencies.fields[x]), keys);
|
|
140
|
-
for (let i = 0; i < keys.length; i++) {
|
|
141
|
-
instance[castKey<T>(keys[i])] = castTo(deps[i]);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Actually construct an instance while resolving the dependencies
|
|
148
|
-
*/
|
|
149
|
-
async construct<T>(target: ClassTarget<T>, qualifier: symbol): Promise<T> {
|
|
150
|
-
const managed = this.resolveTarget(target, qualifier).config;
|
|
151
|
-
|
|
152
|
-
// Only fetch constructor values
|
|
153
|
-
const consValues = await this.fetchDependencies(managed, managed.dependencies.cons);
|
|
154
|
-
|
|
155
|
-
// Create instance
|
|
156
|
-
const inst = managed.factory ?
|
|
157
|
-
managed.factory(...consValues) :
|
|
158
|
-
classConstruct(managed.class, consValues);
|
|
159
|
-
|
|
160
|
-
// And auto-wire fields
|
|
161
|
-
await this.resolveFieldDependencies(managed, inst);
|
|
162
|
-
|
|
163
|
-
// If factory with field properties on the sub class
|
|
164
|
-
if (managed.factory) {
|
|
165
|
-
const resolved = this.get(asConstructable(inst).constructor);
|
|
166
|
-
|
|
167
|
-
if (resolved) {
|
|
168
|
-
await this.resolveFieldDependencies(resolved, inst);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Run post construct, if it wasn't passed in, otherwise it was already created
|
|
173
|
-
if (hasPostConstruct(inst) && !consValues.includes(inst)) {
|
|
174
|
-
await inst.postConstruct();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Run post constructors
|
|
178
|
-
for (const op of Object.values(managed.postConstruct)) {
|
|
179
|
-
await op(inst);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return inst;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Create the instance
|
|
187
|
-
*/
|
|
188
|
-
async createInstance<T>(target: ClassTarget<T>, qualifier: symbol): Promise<T> {
|
|
189
|
-
const classId = this.resolveTarget(target, qualifier).id;
|
|
190
|
-
|
|
191
|
-
if (!this.instances.has(classId)) {
|
|
192
|
-
this.instances.set(classId, new Map());
|
|
193
|
-
this.instancePromises.set(classId, new Map());
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (this.instancePromises.get(classId)!.has(qualifier)) {
|
|
197
|
-
return castTo(this.instancePromises.get(classId)!.get(qualifier));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const instancePromise = this.construct(target, qualifier);
|
|
201
|
-
this.instancePromises.get(classId)!.set(qualifier, instancePromise);
|
|
202
|
-
try {
|
|
203
|
-
const instance = await instancePromise;
|
|
204
|
-
this.instances.get(classId)!.set(qualifier, instance);
|
|
205
|
-
return instance;
|
|
206
|
-
} catch (err) {
|
|
207
|
-
// Clear it out, don't save failed constructions
|
|
208
|
-
this.instancePromises.get(classId)!.delete(qualifier);
|
|
209
|
-
throw err;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Destroy an instance
|
|
215
|
-
*/
|
|
216
|
-
destroyInstance(cls: Class, qualifier: symbol): void {
|
|
217
|
-
const classId = cls.Ⲑid;
|
|
218
|
-
|
|
219
|
-
const activeInstance = this.instances.get(classId)!.get(qualifier);
|
|
220
|
-
if (hasPreDestroy(activeInstance)) {
|
|
221
|
-
activeInstance.preDestroy();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
this.defaultSymbols.delete(qualifier);
|
|
225
|
-
this.instances.get(classId)!.delete(qualifier);
|
|
226
|
-
this.instancePromises.get(classId)!.delete(qualifier);
|
|
227
|
-
this.classToTarget.get(classId)!.delete(qualifier);
|
|
228
|
-
console.debug('On uninstall', { id: classId, qualifier: qualifier.toString(), classId });
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
override async init(): Promise<void> {
|
|
232
|
-
await super.init();
|
|
233
|
-
if (Runtime.dynamic) {
|
|
234
|
-
const { DependencyRegistration } = await import('../support/dynamic.injection.ts');
|
|
235
|
-
DependencyRegistration.init(this);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
await this.getCandidateInstances(AutoCreate);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Handle initial installation for the entire registry
|
|
243
|
-
*/
|
|
244
|
-
override initialInstall(): Class[] {
|
|
245
|
-
const finalizing = this.pendingFinalize;
|
|
246
|
-
this.pendingFinalize = [];
|
|
247
|
-
|
|
248
|
-
for (const cls of finalizing) {
|
|
249
|
-
this.install(cls, { type: 'added', curr: cls });
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return [];
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Register a cls as pending
|
|
257
|
-
*/
|
|
258
|
-
createPending(cls: Class): Partial<InjectableConfig> {
|
|
259
|
-
if (!this.resolved) {
|
|
260
|
-
this.pendingFinalize.push(cls);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
class: cls,
|
|
265
|
-
enabled: true,
|
|
266
|
-
target: cls,
|
|
267
|
-
interfaces: [],
|
|
268
|
-
dependencies: {
|
|
269
|
-
fields: {},
|
|
270
|
-
cons: []
|
|
271
|
-
},
|
|
272
|
-
postConstruct: {}
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Get an instance by type and qualifier
|
|
278
|
-
*/
|
|
279
|
-
async getInstance<T>(target: ClassTarget<T>, qual?: symbol, resolution?: ResolutionType): Promise<T> {
|
|
280
|
-
this.verifyInitialized();
|
|
281
|
-
|
|
282
|
-
if (!target) {
|
|
283
|
-
throw new AppError('Unable to get instance when target is undefined');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const { id: classId, qualifier } = this.resolveTarget(target, qual, resolution);
|
|
287
|
-
if (!this.instances.has(classId) || !this.instances.get(classId)!.has(qualifier)) {
|
|
288
|
-
await this.createInstance(target, qualifier); // Wait for proxy
|
|
289
|
-
}
|
|
290
|
-
return castTo(this.instances.get(classId)!.get(qualifier));
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Get all available candidate types for the target
|
|
295
|
-
*/
|
|
296
|
-
getCandidateTypes<T>(target: Class<T>): InjectableConfig<T>[] {
|
|
297
|
-
const qualifiers = this.targetToClass.get(target.Ⲑid)!;
|
|
298
|
-
const uniqueQualifiers = qualifiers ? Array.from(new Set(qualifiers.values())) : [];
|
|
299
|
-
return castTo(uniqueQualifiers.map(id => this.get(id)));
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Get candidate instances by target type, with an optional filter
|
|
304
|
-
*/
|
|
305
|
-
getCandidateInstances<T>(target: Class<T>, predicate?: (cfg: InjectableConfig<T>) => boolean): Promise<T[]> {
|
|
306
|
-
const inputs = this.getCandidateTypes<T>(target).filter(x => !predicate || predicate(x));
|
|
307
|
-
return Promise.all(inputs.map(l => this.getInstance<T>(l.class, l.qualifier)));
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Register a constructor with dependencies
|
|
312
|
-
*/
|
|
313
|
-
registerConstructor<T>(cls: Class<T>, dependencies?: Dependency[]): void {
|
|
314
|
-
const conf = this.getOrCreatePending(cls);
|
|
315
|
-
conf.dependencies!.cons = dependencies;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Register a post construct handler
|
|
320
|
-
*/
|
|
321
|
-
registerPostConstructHandler<T>(cls: Class<T>, name: string, handler: PostConstructHandler<T>): void {
|
|
322
|
-
const conf = this.getOrCreatePending(cls);
|
|
323
|
-
conf.postConstruct![name] = castTo(handler);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Register a property as a dependency
|
|
328
|
-
*/
|
|
329
|
-
registerProperty<T>(cls: Class<T>, field: string, dependency: Dependency): void {
|
|
330
|
-
const conf = this.getOrCreatePending(cls);
|
|
331
|
-
conf.dependencies!.fields[field] = dependency;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Register a class
|
|
336
|
-
*/
|
|
337
|
-
registerClass<T>(cls: Class<T>, pConfig: Partial<InjectableConfig<T>> = {}): void {
|
|
338
|
-
const config = this.getOrCreatePending(pConfig.class ?? cls);
|
|
339
|
-
|
|
340
|
-
config.enabled = pConfig.enabled ?? config.enabled;
|
|
341
|
-
config.class = cls;
|
|
342
|
-
config.qualifier = pConfig.qualifier ?? config.qualifier ?? Symbol.for(cls.Ⲑid);
|
|
343
|
-
if (pConfig.interfaces) {
|
|
344
|
-
(config.interfaces ??= []).push(...pConfig.interfaces);
|
|
345
|
-
}
|
|
346
|
-
if (pConfig.primary !== undefined) {
|
|
347
|
-
config.primary = pConfig.primary;
|
|
348
|
-
}
|
|
349
|
-
if (pConfig.factory) {
|
|
350
|
-
config.factory = pConfig.factory ?? config.factory;
|
|
351
|
-
}
|
|
352
|
-
if (pConfig.target) {
|
|
353
|
-
config.target = pConfig.target;
|
|
354
|
-
}
|
|
355
|
-
if (pConfig.dependencies) {
|
|
356
|
-
config.dependencies = {
|
|
357
|
-
...pConfig.dependencies,
|
|
358
|
-
fields: {
|
|
359
|
-
...pConfig.dependencies.fields
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
if (pConfig.autoCreate) {
|
|
364
|
-
(config.interfaces ??= []).push(AutoCreate);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Register a factory configuration
|
|
370
|
-
*/
|
|
371
|
-
registerFactory(config: Omit<InjectableFactoryConfig, 'qualifier'> & {
|
|
372
|
-
id: string;
|
|
373
|
-
qualifier?: undefined | symbol;
|
|
374
|
-
fn: TypedFunction;
|
|
375
|
-
}): void {
|
|
376
|
-
const finalConfig: Partial<InjectableConfig> = {};
|
|
377
|
-
|
|
378
|
-
finalConfig.enabled = config.enabled ?? true;
|
|
379
|
-
finalConfig.factory = config.fn;
|
|
380
|
-
finalConfig.target = config.target;
|
|
381
|
-
finalConfig.qualifier = config.qualifier;
|
|
382
|
-
if (!finalConfig.qualifier) {
|
|
383
|
-
finalConfig.qualifier = Symbol.for(config.id);
|
|
384
|
-
}
|
|
385
|
-
if (config.primary !== undefined) {
|
|
386
|
-
finalConfig.primary = config.primary;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
finalConfig.dependencies = { fields: {} };
|
|
390
|
-
|
|
391
|
-
if (config.dependencies) {
|
|
392
|
-
finalConfig.dependencies.cons = config.dependencies;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Create mock cls for DI purposes
|
|
396
|
-
const fnClass = class { static Ⲑid = config.id; };
|
|
397
|
-
|
|
398
|
-
finalConfig.class = fnClass;
|
|
399
|
-
|
|
400
|
-
this.registerClass(fnClass, finalConfig);
|
|
401
|
-
|
|
402
|
-
const srcClassId = config.src.Ⲑid;
|
|
403
|
-
|
|
404
|
-
if (!this.factories.has(srcClassId)) {
|
|
405
|
-
this.factories.set(srcClassId, new Map());
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
this.factories.get(srcClassId)!.set(fnClass, asFull(finalConfig));
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* On Install event
|
|
413
|
-
*/
|
|
414
|
-
override onInstall<T>(cls: Class<T>, e: ChangeEvent<Class<T>>): void {
|
|
415
|
-
super.onInstall(cls, e);
|
|
416
|
-
const classId = cls.Ⲑid;
|
|
417
|
-
|
|
418
|
-
// Install factories separate from classes
|
|
419
|
-
if (this.factories.has(classId)) {
|
|
420
|
-
for (const fact of this.factories.get(classId)!.keys()) {
|
|
421
|
-
this.onInstall(fact, e);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Handle installing a class
|
|
428
|
-
*/
|
|
429
|
-
onInstallFinalize<T>(cls: Class<T>): InjectableConfig<T> {
|
|
430
|
-
const classId = cls.Ⲑid;
|
|
431
|
-
|
|
432
|
-
const config: InjectableConfig<T> = castTo(this.getOrCreatePending(cls));
|
|
433
|
-
|
|
434
|
-
if (config.enabled !== undefined && !(typeof config.enabled === 'boolean' ? config.enabled : config.enabled())) {
|
|
435
|
-
return config; // Do not setup if disabled
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Allow for the factory to fulfill the target
|
|
439
|
-
let parentClass: Function = config.factory ? config.target : Object.getPrototypeOf(cls);
|
|
440
|
-
|
|
441
|
-
if (config.factory) {
|
|
442
|
-
while (describeFunction(Object.getPrototypeOf(parentClass))?.abstract) {
|
|
443
|
-
parentClass = Object.getPrototypeOf(parentClass);
|
|
444
|
-
}
|
|
445
|
-
if (!this.targetToClass.has(classId)) {
|
|
446
|
-
this.targetToClass.set(classId, new Map());
|
|
447
|
-
}
|
|
448
|
-
// Make explicitly discoverable as self
|
|
449
|
-
this.targetToClass.get(classId)?.set(config.qualifier, classId);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const parentConfig = this.get(parentClass.Ⲑid);
|
|
453
|
-
|
|
454
|
-
if (parentConfig) {
|
|
455
|
-
config.dependencies.fields = {
|
|
456
|
-
...parentConfig.dependencies!.fields,
|
|
457
|
-
...config.dependencies.fields
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
// collect interfaces
|
|
461
|
-
config.interfaces = [
|
|
462
|
-
...parentConfig.interfaces,
|
|
463
|
-
...config.interfaces
|
|
464
|
-
];
|
|
465
|
-
|
|
466
|
-
config.postConstruct = {
|
|
467
|
-
...parentConfig.postConstruct,
|
|
468
|
-
...config.postConstruct
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// Inherit cons deps if no constructor defined
|
|
472
|
-
if (config.dependencies.cons === undefined) {
|
|
473
|
-
config.dependencies.cons = parentConfig.dependencies.cons;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (describeFunction(cls)?.abstract) { // Skip out early, only needed to inherit
|
|
478
|
-
return config;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (!this.classToTarget.has(classId)) {
|
|
482
|
-
this.classToTarget.set(classId, new Map());
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const targetClassId = config.target.Ⲑid;
|
|
486
|
-
|
|
487
|
-
if (!this.targetToClass.has(targetClassId)) {
|
|
488
|
-
this.targetToClass.set(targetClassId, new Map());
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (config.qualifier === Symbol.for(classId)) {
|
|
492
|
-
this.defaultSymbols.add(config.qualifier);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
this.targetToClass.get(targetClassId)!.set(config.qualifier, classId);
|
|
496
|
-
this.classToTarget.get(classId)!.set(config.qualifier, targetClassId);
|
|
497
|
-
|
|
498
|
-
// If aliased
|
|
499
|
-
for (const el of config.interfaces) {
|
|
500
|
-
const elClassId = el.Ⲑid;
|
|
501
|
-
if (!this.targetToClass.has(elClassId)) {
|
|
502
|
-
this.targetToClass.set(elClassId, new Map());
|
|
503
|
-
}
|
|
504
|
-
this.targetToClass.get(elClassId)!.set(config.qualifier, classId);
|
|
505
|
-
this.classToTarget.get(classId)!.set(Symbol.for(elClassId), elClassId);
|
|
506
|
-
|
|
507
|
-
if (config.primary && (classId === targetClassId || config.factory)) {
|
|
508
|
-
this.targetToClass.get(elClassId)!.set(PrimaryCandidateSymbol, classId);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// If targeting self (default @Injectable behavior)
|
|
513
|
-
if ((classId === targetClassId || config.factory) && (parentConfig || describeFunction(parentClass)?.abstract)) {
|
|
514
|
-
const parentId = parentClass.Ⲑid;
|
|
515
|
-
|
|
516
|
-
if (!this.targetToClass.has(parentId)) {
|
|
517
|
-
this.targetToClass.set(parentId, new Map());
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (config.primary) {
|
|
521
|
-
this.targetToClass.get(parentId)!.set(PrimaryCandidateSymbol, classId);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
this.targetToClass.get(parentId)!.set(config.qualifier, classId);
|
|
525
|
-
this.classToTarget.get(classId)!.set(config.qualifier, parentId);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (config.primary) {
|
|
529
|
-
if (!this.targetToClass.has(classId)) {
|
|
530
|
-
this.targetToClass.set(classId, new Map());
|
|
531
|
-
}
|
|
532
|
-
this.targetToClass.get(classId)!.set(PrimaryCandidateSymbol, classId);
|
|
533
|
-
|
|
534
|
-
if (config.factory) {
|
|
535
|
-
this.targetToClass.get(targetClassId)!.set(PrimaryCandidateSymbol, classId);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Register primary if only one interface provided and no parent config
|
|
539
|
-
if (config.interfaces.length === 1 && !parentConfig) {
|
|
540
|
-
const [primaryInterface] = config.interfaces;
|
|
541
|
-
const primaryClassId = primaryInterface.Ⲑid;
|
|
542
|
-
if (!this.targetToClass.has(primaryClassId)) {
|
|
543
|
-
this.targetToClass.set(primaryClassId, new Map());
|
|
544
|
-
}
|
|
545
|
-
this.targetToClass.get(primaryClassId)!.set(PrimaryCandidateSymbol, classId);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
return config;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Handle uninstalling a class
|
|
554
|
-
*/
|
|
555
|
-
override onUninstallFinalize(cls: Class): void {
|
|
556
|
-
const classId = cls.Ⲑid;
|
|
557
|
-
|
|
558
|
-
if (!this.classToTarget.has(classId)) {
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (this.instances.has(classId)) {
|
|
563
|
-
for (const qualifier of this.classToTarget.get(classId)!.keys()) {
|
|
564
|
-
this.destroyInstance(cls, qualifier);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Inject fields into instance
|
|
571
|
-
*/
|
|
572
|
-
async injectFields<T extends { constructor: Class<T> }>(o: T, cls = o.constructor): Promise<void> {
|
|
573
|
-
this.verifyInitialized();
|
|
574
|
-
// Compute fields to be auto-wired
|
|
575
|
-
return await this.resolveFieldDependencies(this.get(cls), o);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
export const DependencyRegistry = new $DependencyRegistry();
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { Class, describeFunction } from '@travetto/runtime';
|
|
2
|
-
import { RetargettingProxy } from '@travetto/registry';
|
|
3
|
-
|
|
4
|
-
import type { DependencyRegistry, ResolutionType, Resolved } from '../src/registry.ts';
|
|
5
|
-
import type { ClassTarget, InjectableConfig } from '../src/types.ts';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Wraps the Dependency Registry to support proxying instances
|
|
9
|
-
*/
|
|
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
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Proxy the created instance
|
|
20
|
-
*/
|
|
21
|
-
proxyInstance<T>(target: ClassTarget<T>, qual: symbol | undefined, instance: T): T {
|
|
22
|
-
const { qualifier, id: classId } = this.#registryResolveTarget(target, qual);
|
|
23
|
-
let proxy: RetargettingProxy<unknown>;
|
|
24
|
-
|
|
25
|
-
if (!this.#proxies.has(classId)) {
|
|
26
|
-
this.#proxies.set(classId, new Map());
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!this.#proxies.get(classId)!.has(qualifier)) {
|
|
30
|
-
proxy = new RetargettingProxy<T>(instance);
|
|
31
|
-
this.#proxies.get(classId)!.set(qualifier, proxy);
|
|
32
|
-
if (this.#registry.trace) {
|
|
33
|
-
console.debug('Registering proxy', { id: target.Ⲑid, qualifier: qualifier.toString() });
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
proxy = this.#proxies.get(classId)!.get(qualifier)!;
|
|
37
|
-
proxy.setTarget(instance);
|
|
38
|
-
if (this.#registry.trace) {
|
|
39
|
-
console.debug('Updating target', {
|
|
40
|
-
id: target.Ⲑid, qualifier: qualifier.toString(), instanceType: target.name
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return proxy.get<T>();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Create instance and wrap in a proxy
|
|
50
|
-
*/
|
|
51
|
-
async createInstance<T>(target: ClassTarget<T>, qualifier: symbol): Promise<T> {
|
|
52
|
-
const instance = await this.#registryCreateInstance(target, qualifier);
|
|
53
|
-
const classId = this.#registryResolveTarget(target, qualifier).id;
|
|
54
|
-
// Reset as proxy instance
|
|
55
|
-
const proxied = this.proxyInstance<T>(target, qualifier, instance);
|
|
56
|
-
this.#registry['instances'].get(classId)!.set(qualifier, proxied);
|
|
57
|
-
return proxied;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Reload proxy if in watch mode
|
|
62
|
-
*/
|
|
63
|
-
onInstallFinalize<T>(cls: Class<T>): InjectableConfig<T> {
|
|
64
|
-
const config = this.#registryOnInstallFinalize(cls);
|
|
65
|
-
// If already loaded, reload
|
|
66
|
-
const classId = cls.Ⲑid;
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
!describeFunction(cls)?.abstract &&
|
|
70
|
-
this.#proxies.has(classId) &&
|
|
71
|
-
this.#proxies.get(classId)!.has(config.qualifier)
|
|
72
|
-
) {
|
|
73
|
-
console.debug('Reloading on next tick');
|
|
74
|
-
// Timing matters due to create instance being asynchronous
|
|
75
|
-
process.nextTick(() => this.createInstance(config.target, config.qualifier));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return config;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
destroyInstance(cls: Class, qualifier: symbol): void {
|
|
82
|
-
const classId = cls.Ⲑid;
|
|
83
|
-
const proxy = this.#proxies.get(classId)?.get(qualifier);
|
|
84
|
-
this.#registryDestroyInstance(cls, qualifier);
|
|
85
|
-
if (proxy) {
|
|
86
|
-
proxy.setTarget(null);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
register(registry: typeof DependencyRegistry): void {
|
|
91
|
-
this.#registry = registry;
|
|
92
|
-
this.#registryCreateInstance = registry.createInstance.bind(registry);
|
|
93
|
-
this.#registryResolveTarget = registry.resolveTarget.bind(registry);
|
|
94
|
-
this.#registryOnInstallFinalize = registry.onInstallFinalize.bind(registry);
|
|
95
|
-
this.#registryDestroyInstance = registry.destroyInstance.bind(registry);
|
|
96
|
-
|
|
97
|
-
this.#registry.createInstance = this.createInstance.bind(this);
|
|
98
|
-
this.#registry.destroyInstance = this.destroyInstance.bind(this);
|
|
99
|
-
this.#registry.onInstallFinalize = this.onInstallFinalize.bind(this);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const DependencyRegistration = {
|
|
104
|
-
init(registry: typeof DependencyRegistry): void {
|
|
105
|
-
const dynamic = new $DynamicDependencyRegistry();
|
|
106
|
-
dynamic.register(registry);
|
|
107
|
-
}
|
|
108
|
-
};
|