@travetto/di 8.0.0-alpha.0 → 8.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/decorator.ts +12 -2
- package/src/registry/registry-adapter.ts +11 -10
- package/src/registry/registry-index.ts +33 -68
- package/src/registry/registry-resolver.ts +5 -1
- package/src/types.ts +12 -5
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#L17). 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": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dependency registration/management and injection support.",
|
|
6
6
|
"keywords": [
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"directory": "module/di"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@travetto/registry": "^8.0.0-alpha.
|
|
31
|
+
"@travetto/registry": "^8.0.0-alpha.10"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@travetto/transformer": "^8.0.0-alpha.
|
|
34
|
+
"@travetto/transformer": "^8.0.0-alpha.5"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {
|
|
37
37
|
"@travetto/transformer": {
|
package/src/decorator.ts
CHANGED
|
@@ -15,7 +15,7 @@ const fromInput = <T extends { qualifier?: symbol }>(input?: T | symbol): T =>
|
|
|
15
15
|
*/
|
|
16
16
|
export function Injectable(input?: Partial<InjectableCandidate> | symbol) {
|
|
17
17
|
return <T extends Class>(cls: T): void => {
|
|
18
|
-
DependencyRegistryIndex.
|
|
18
|
+
DependencyRegistryIndex.registerClass(cls, fromInput(input));
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -47,8 +47,18 @@ export function Inject(input?: InjectConfig | symbol) {
|
|
|
47
47
|
*/
|
|
48
48
|
export function InjectableFactory(input?: Partial<InjectableCandidate> | symbol) {
|
|
49
49
|
return <T extends Class>(cls: T, property: string, descriptor: TypedPropertyDescriptor<(...args: Any[]) => Any>): void => {
|
|
50
|
-
DependencyRegistryIndex.
|
|
50
|
+
DependencyRegistryIndex.registerFactory(cls, property, fromInput(input), {
|
|
51
51
|
factory: (...params: unknown[]) => descriptor.value!.apply(cls, params),
|
|
52
52
|
});
|
|
53
53
|
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Register post construction handler
|
|
58
|
+
* @kind decorator
|
|
59
|
+
*/
|
|
60
|
+
export function PostConstruct(priority: number = 10) {
|
|
61
|
+
return (instance: ClassInstance, property: string, descriptor: TypedPropertyDescriptor<() => Any>): void => {
|
|
62
|
+
DependencyRegistryIndex.registerPostConstruct(getClass(instance), { operation: descriptor.value!, priority });
|
|
63
|
+
};
|
|
54
64
|
}
|
|
@@ -13,14 +13,12 @@ function combineInjectableCandidates<T extends InjectableCandidate>(base: T, ...
|
|
|
13
13
|
|
|
14
14
|
function combineClasses<T extends InjectableConfig>(base: T, ...overrides: Partial<T>[]): typeof base {
|
|
15
15
|
for (const override of overrides) {
|
|
16
|
-
|
|
17
|
-
...base,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
});
|
|
16
|
+
if (override.candidates) {
|
|
17
|
+
base.candidates = { ...base.candidates, ...override.candidates };
|
|
18
|
+
}
|
|
19
|
+
if (override.postConstruct) {
|
|
20
|
+
base.postConstruct = [...base.postConstruct, ...override.postConstruct];
|
|
21
|
+
}
|
|
24
22
|
}
|
|
25
23
|
return base;
|
|
26
24
|
}
|
|
@@ -34,7 +32,7 @@ export class DependencyRegistryAdapter implements RegistryAdapter<InjectableConf
|
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
register(...data: Partial<InjectableConfig<unknown>>[]): InjectableConfig<unknown> {
|
|
37
|
-
this.#config ??= { class: this.#cls, candidates: {} };
|
|
35
|
+
this.#config ??= { class: this.#cls, candidates: {}, postConstruct: [] };
|
|
38
36
|
return combineClasses(this.#config, ...data);
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -61,13 +59,16 @@ export class DependencyRegistryAdapter implements RegistryAdapter<InjectableConf
|
|
|
61
59
|
return this.#config;
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
finalize(): void {
|
|
62
|
+
finalize(parent?: InjectableConfig): void {
|
|
65
63
|
for (const method of Object.keys(this.#config.candidates)) {
|
|
66
64
|
const candidate = this.#config.candidates[method];
|
|
67
65
|
const candidateType = SchemaRegistryIndex.get(candidate.class).getMethodReturnType(method);
|
|
68
66
|
candidate.candidateType = candidateType;
|
|
69
67
|
candidate.qualifier ??= getDefaultQualifier(candidateType);
|
|
70
68
|
}
|
|
69
|
+
// Inherit post construct from parent
|
|
70
|
+
this.#config.postConstruct = [...(parent?.postConstruct ?? []), ...this.#config.postConstruct]
|
|
71
|
+
.sort((a, b) => (a.priority ?? 1) - (b.priority ?? 1));
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
getCandidateConfigs(): InjectableCandidate[] {
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { type RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { castKey, castTo, type Class, describeFunction, getParentClass, TypedObject } from '@travetto/runtime';
|
|
3
3
|
import { type SchemaFieldConfig, type SchemaParameterConfig, SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
|
-
import type { Dependency, InjectableCandidate,
|
|
5
|
+
import type { Dependency, InjectableCandidate, InjectableConfig, PostConstructor, ResolutionType } from '../types.ts';
|
|
6
6
|
import { DependencyRegistryAdapter } from './registry-adapter.ts';
|
|
7
7
|
import { InjectionError } from '../error.ts';
|
|
8
8
|
import { DependencyRegistryResolver } from './registry-resolver.ts';
|
|
9
9
|
|
|
10
10
|
const MetadataSymbol = Symbol();
|
|
11
|
-
|
|
12
|
-
const hasPostConstruct = hasFunction<{ postConstruct: () => Promise<unknown> }>('postConstruct');
|
|
13
|
-
const hasPreDestroy = hasFunction<{ preDestroy: () => Promise<unknown> }>('preDestroy');
|
|
11
|
+
const PostConstructionRan = Symbol();
|
|
14
12
|
|
|
15
13
|
function readMetadata(item: { metadata?: Record<symbol, unknown> }): Dependency | undefined {
|
|
16
14
|
return castTo<Dependency | undefined>(item.metadata?.[MetadataSymbol]);
|
|
@@ -24,6 +22,18 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
24
22
|
return this.#instance.store.getForRegister(cls);
|
|
25
23
|
}
|
|
26
24
|
|
|
25
|
+
static registerPostConstruct<T>(cls: Class<T>, handler: PostConstructor<T>): void {
|
|
26
|
+
this.#instance.store.getForRegister(cls).register({ postConstruct: [castTo(handler)] });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static registerClass(cls: Class, ...data: Partial<InjectableCandidate<unknown>>[]): InjectableCandidate {
|
|
30
|
+
return this.#instance.store.getForRegister(cls).registerClass(...data);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static registerFactory(cls: Class, method: string, ...data: Partial<InjectableCandidate<unknown>>[]): InjectableCandidate {
|
|
34
|
+
return this.#instance.store.getForRegister(cls).registerFactory(method, ...data);
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
static getInstance<T>(candidateType: Class<T>, qualifier?: symbol, resolution?: ResolutionType): Promise<T> {
|
|
28
38
|
return this.#instance.getInstance(candidateType, qualifier, resolution);
|
|
29
39
|
}
|
|
@@ -40,10 +50,6 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
40
50
|
return this.#instance.injectFields(cls, item, cls);
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
static registerClassMetadata(cls: Class, metadata: InjectableClassMetadata): void {
|
|
44
|
-
SchemaRegistryIndex.getForRegister(cls).registerMetadata<InjectableClassMetadata>(MetadataSymbol, metadata);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
53
|
static registerParameterMetadata(cls: Class, method: string, index: number, metadata: Dependency): void {
|
|
48
54
|
SchemaRegistryIndex.getForRegister(cls).registerParameterMetadata(method, index, MetadataSymbol, metadata);
|
|
49
55
|
}
|
|
@@ -52,7 +58,6 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
52
58
|
SchemaRegistryIndex.getForRegister(cls).registerFieldMetadata(field, MetadataSymbol, metadata);
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
#instances = new Map<Class, Map<symbol, unknown>>();
|
|
56
61
|
#instancePromises = new Map<Class, Map<symbol, Promise<unknown>>>();
|
|
57
62
|
#resolver = new DependencyRegistryResolver();
|
|
58
63
|
|
|
@@ -135,12 +140,12 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
135
140
|
/**
|
|
136
141
|
* Retrieve mapped dependencies
|
|
137
142
|
*/
|
|
138
|
-
async injectFields<T>(candidateType: Class, instance: T,
|
|
143
|
+
async injectFields<T>(candidateType: Class, instance: T, sourceClass: Class): Promise<T> {
|
|
139
144
|
const inputs = SchemaRegistryIndex.getOptional(candidateType)?.getFields() ?? {};
|
|
140
145
|
|
|
141
146
|
const promises = TypedObject.entries(inputs)
|
|
142
147
|
.filter(([key, input]) => readMetadata(input) !== undefined && (input.access !== 'readonly' && instance[castKey(key)] === undefined))
|
|
143
|
-
.map(async ([key, input]) => [key, await this.#resolveDependencyValue(readMetadata(input) ?? {}, input,
|
|
148
|
+
.map(async ([key, input]) => [key, await this.#resolveDependencyValue(readMetadata(input) ?? {}, input, sourceClass)] as const);
|
|
144
149
|
|
|
145
150
|
const pairs = await Promise.all(promises);
|
|
146
151
|
|
|
@@ -155,76 +160,36 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
155
160
|
*/
|
|
156
161
|
async construct<T>(candidateType: Class<T>, qualifier: symbol): Promise<T> {
|
|
157
162
|
const { candidate } = this.#resolver.resolveCandidate(candidateType, qualifier);
|
|
158
|
-
const targetType = candidate
|
|
163
|
+
const { candidateType: targetType } = candidate;
|
|
159
164
|
const params = await this.fetchDependencyParameters(candidate);
|
|
160
|
-
const
|
|
165
|
+
const instance = await candidate.factory(...params);
|
|
161
166
|
|
|
162
167
|
// And auto-wire fields
|
|
163
|
-
await this.injectFields(targetType,
|
|
164
|
-
|
|
165
|
-
// Run post construct, if it wasn't passed in, otherwise it was already created
|
|
166
|
-
if (hasPostConstruct(inst) && !params.includes(inst)) {
|
|
167
|
-
await inst.postConstruct();
|
|
168
|
-
}
|
|
168
|
+
await this.injectFields(targetType, instance, candidate.class);
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
// Run post constructors if not run yet
|
|
171
|
+
const constructed: T & { [PostConstructionRan]?: boolean } = instance!;
|
|
172
|
+
if (!constructed[PostConstructionRan]) {
|
|
173
|
+
constructed[PostConstructionRan] = true;
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
// Run post constructors
|
|
176
|
+
const postConstruct = this.store.has(targetType) ? this.getConfig(targetType).postConstruct : [];
|
|
177
|
+
for (const { operation } of postConstruct) {
|
|
178
|
+
await operation.call(instance);
|
|
179
|
+
}
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
|
|
179
|
-
return inst;
|
|
182
|
+
return instance;
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
/**
|
|
183
186
|
* Get or create the instance
|
|
184
187
|
*/
|
|
185
188
|
async getInstance<T>(candidateType: Class<T>, requestedQualifier?: symbol, resolution?: ResolutionType): Promise<T> {
|
|
186
|
-
if (!candidateType) {
|
|
187
|
-
throw new RuntimeError('Unable to get instance when target is undefined');
|
|
188
|
-
}
|
|
189
|
-
|
|
190
189
|
const { target, qualifier } = this.#resolver.resolveCandidate(candidateType, requestedQualifier, resolution);
|
|
191
190
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (instancePromises.has(qualifier)) {
|
|
196
|
-
return castTo(instancePromises.get(qualifier));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const instancePromise = this.construct(candidateType, qualifier);
|
|
200
|
-
instancePromises.set(qualifier, instancePromise);
|
|
201
|
-
try {
|
|
202
|
-
const instance = await instancePromise;
|
|
203
|
-
instances.set(qualifier, instance);
|
|
204
|
-
return instance;
|
|
205
|
-
} catch (error) {
|
|
206
|
-
// Clear it out, don't save failed constructions
|
|
207
|
-
instancePromises.delete(qualifier);
|
|
208
|
-
throw error;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Destroy an instance
|
|
214
|
-
*/
|
|
215
|
-
destroyInstance(candidateType: Class, requestedQualifier: symbol): void {
|
|
216
|
-
const { target, qualifier } = this.#resolver.resolveCandidate(candidateType, requestedQualifier);
|
|
217
|
-
|
|
218
|
-
const activeInstance = this.#instances.get(target)?.get(qualifier);
|
|
219
|
-
if (hasPreDestroy(activeInstance)) {
|
|
220
|
-
activeInstance.preDestroy();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
this.#resolver.removeClass(candidateType, qualifier);
|
|
224
|
-
this.#instances.get(target)?.delete(qualifier);
|
|
225
|
-
this.#instancePromises.get(target)?.delete(qualifier);
|
|
226
|
-
|
|
227
|
-
// May not exist
|
|
228
|
-
console.debug('On uninstall', { id: target, qualifier: qualifier.toString(), classId: target });
|
|
191
|
+
return castTo(this.#instancePromises
|
|
192
|
+
.getOrInsert(target, new Map())
|
|
193
|
+
.getOrInsertComputed(qualifier, () => this.construct(target, qualifier)));
|
|
229
194
|
}
|
|
230
195
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
1
|
+
import { SchemaRegistryIndex, UnknownType } from '@travetto/schema';
|
|
2
2
|
import { castTo, type Class } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import { getDefaultQualifier, type InjectableCandidate, PrimaryCandidateSymbol, type ResolutionType } from '../types.ts';
|
|
@@ -122,6 +122,10 @@ export class DependencyRegistryResolver {
|
|
|
122
122
|
* @param qualifier
|
|
123
123
|
*/
|
|
124
124
|
resolveCandidate<T>(candidateType: Class<T>, qualifier?: symbol, resolution?: ResolutionType): Resolved<T> {
|
|
125
|
+
if (!candidateType) {
|
|
126
|
+
throw new InjectionError('Unable to resolve candidate when target is undefined', UnknownType, qualifier ? [qualifier] : undefined);
|
|
127
|
+
}
|
|
128
|
+
|
|
125
129
|
const qualifiers = this.#byCandidateType.get(candidateType) ?? new Map<symbol, InjectableCandidate>();
|
|
126
130
|
|
|
127
131
|
let config: InjectableCandidate;
|
package/src/types.ts
CHANGED
|
@@ -63,6 +63,13 @@ export interface InjectableCandidate<T = unknown> {
|
|
|
63
63
|
qualifier?: symbol;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** Post Construct Handler */
|
|
67
|
+
export type PostConstructor<T> = {
|
|
68
|
+
operation: ((this: T) => Promise<unknown> | unknown);
|
|
69
|
+
priority: number;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
|
|
66
73
|
/**
|
|
67
74
|
* Full injectable configuration for a class
|
|
68
75
|
*/
|
|
@@ -75,14 +82,14 @@ export interface InjectableConfig<T = unknown> {
|
|
|
75
82
|
* Candidates that are injectable
|
|
76
83
|
*/
|
|
77
84
|
candidates: Record<string, InjectableCandidate>;
|
|
85
|
+
/**
|
|
86
|
+
* Post construct methods to run after construction of the instance
|
|
87
|
+
*/
|
|
88
|
+
postConstruct: PostConstructor<T>[];
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
export function getDefaultQualifier(cls: Class): symbol {
|
|
81
92
|
return Symbol.for(cls.Ⲑid);
|
|
82
93
|
}
|
|
83
94
|
|
|
84
|
-
export const PrimaryCandidateSymbol = Symbol();
|
|
85
|
-
|
|
86
|
-
export type InjectableClassMetadata = {
|
|
87
|
-
postConstruct: Record<string, (<T>(inst: T) => Promise<void>)>;
|
|
88
|
-
};
|
|
95
|
+
export const PrimaryCandidateSymbol = Symbol();
|