@travetto/di 7.0.0-rc.1 → 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 +5 -5
- 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 +29 -29
- package/src/registry/registry-resolver.ts +9 -7
- 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()
|
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
|
}
|
|
@@ -33,15 +33,15 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
static getCandidateTypes<T>(candidateType: Class<T>): Class<T>[] {
|
|
36
|
-
return this.#instance.getCandidates(candidateType).map(
|
|
36
|
+
return this.#instance.getCandidates(candidateType).map(candidate => candidate.candidateType);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
static getInstances<T>(candidateType: Class<T>, predicate?: (
|
|
39
|
+
static getInstances<T>(candidateType: Class<T>, predicate?: (config: InjectableCandidate<T>) => boolean): Promise<T[]> {
|
|
40
40
|
return this.#instance.getInstances<T>(candidateType, predicate);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
static injectFields<T extends { constructor: Class<T> }>(
|
|
44
|
-
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);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
static getOptional(cls: Class): InjectableConfig | undefined {
|
|
@@ -52,11 +52,11 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
52
52
|
SchemaRegistryIndex.getForRegister(cls).registerMetadata<InjectableClassMetadata>(MetadataSymbol, metadata);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
static registerParameterMetadata(cls: Class, method: string
|
|
55
|
+
static registerParameterMetadata(cls: Class, method: string, index: number, metadata: Dependency): void {
|
|
56
56
|
SchemaRegistryIndex.getForRegister(cls).registerParameterMetadata(method, index, MetadataSymbol, metadata);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
static registerFieldMetadata(cls: Class, field: string
|
|
59
|
+
static registerFieldMetadata(cls: Class, field: string, metadata: Dependency): void {
|
|
60
60
|
SchemaRegistryIndex.getForRegister(cls).registerFieldMetadata(field, MetadataSymbol, metadata);
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -117,18 +117,18 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
async #resolveDependencyValue(dependency: Dependency, input: SchemaFieldConfig | SchemaParameterConfig,
|
|
120
|
+
async #resolveDependencyValue(dependency: Dependency, input: SchemaFieldConfig | SchemaParameterConfig, cls: Class): Promise<unknown> {
|
|
121
121
|
try {
|
|
122
122
|
const target = dependency.target ?? input.type;
|
|
123
123
|
return await this.getInstance(target, dependency.qualifier, dependency.resolution);
|
|
124
|
-
} catch (
|
|
125
|
-
if (input.required?.active === false &&
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (input.required?.active === false && error instanceof InjectionError && error.category === 'notfound') {
|
|
126
126
|
return undefined;
|
|
127
127
|
} else {
|
|
128
|
-
if (
|
|
129
|
-
|
|
128
|
+
if (error && error instanceof Error) {
|
|
129
|
+
error.message = `${error.message} via=${cls.Ⲑid}[${input.name?.toString() ?? 'constructor'}]`;
|
|
130
130
|
}
|
|
131
|
-
throw
|
|
131
|
+
throw error;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -140,12 +140,12 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
process(events: ChangeEvent<Class>[]): void {
|
|
143
|
-
for (const
|
|
144
|
-
if ('
|
|
145
|
-
this.#removeClass(
|
|
143
|
+
for (const event of events) {
|
|
144
|
+
if ('previous' in event) {
|
|
145
|
+
this.#removeClass(event.previous);
|
|
146
146
|
}
|
|
147
|
-
if ('
|
|
148
|
-
this.#addClass(
|
|
147
|
+
if ('current' in event) {
|
|
148
|
+
this.#addClass(event.current, 'previous' in event);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
}
|
|
@@ -154,15 +154,15 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
154
154
|
* Get all available candidates for a given type
|
|
155
155
|
*/
|
|
156
156
|
getCandidates<T>(candidateType: Class<T>): InjectableCandidate<T>[] {
|
|
157
|
-
return this.#resolver.getCandidateEntries(candidateType).map(([_,
|
|
157
|
+
return this.#resolver.getCandidateEntries(candidateType).map(([_, candidate]) => castTo<InjectableCandidate<T>>(candidate));
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
161
|
* Get candidate instances by target type, with an optional filter
|
|
162
162
|
*/
|
|
163
|
-
getInstances<T>(candidateType: Class<T>, predicate?: (
|
|
164
|
-
const inputs = this.getCandidates<T>(candidateType).filter(
|
|
165
|
-
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)));
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
@@ -185,13 +185,13 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
185
185
|
const inputs = SchemaRegistryIndex.getOptional(candidateType)?.getFields() ?? {};
|
|
186
186
|
|
|
187
187
|
const promises = TypedObject.entries(inputs)
|
|
188
|
-
.filter(([
|
|
189
|
-
.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);
|
|
190
190
|
|
|
191
191
|
const pairs = await Promise.all(promises);
|
|
192
192
|
|
|
193
|
-
for (const [
|
|
194
|
-
instance[castKey(
|
|
193
|
+
for (const [key, value] of pairs) {
|
|
194
|
+
instance[castKey(key)] = castTo(value);
|
|
195
195
|
}
|
|
196
196
|
return instance;
|
|
197
197
|
}
|
|
@@ -217,8 +217,8 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
217
217
|
SchemaRegistryIndex.get(targetType).getMetadata<InjectableClassMetadata>(MetadataSymbol) : undefined;
|
|
218
218
|
|
|
219
219
|
// Run post constructors
|
|
220
|
-
for (const
|
|
221
|
-
await
|
|
220
|
+
for (const operation of Object.values(metadata?.postConstruct ?? {})) {
|
|
221
|
+
await operation(inst);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
// Proxy if necessary
|
|
@@ -250,10 +250,10 @@ export class DependencyRegistryIndex implements RegistryIndex {
|
|
|
250
250
|
const instance = await instancePromise;
|
|
251
251
|
this.#instances.get(target)!.set(qualifier, instance);
|
|
252
252
|
return instance;
|
|
253
|
-
} catch (
|
|
253
|
+
} catch (error) {
|
|
254
254
|
// Clear it out, don't save failed constructions
|
|
255
255
|
this.#instancePromises.get(target)!.delete(qualifier);
|
|
256
|
-
throw
|
|
256
|
+
throw error;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -6,11 +6,11 @@ import { InjectionError } from '../error';
|
|
|
6
6
|
|
|
7
7
|
type Resolved<T> = { candidate: InjectableCandidate<T>, qualifier: symbol, target: Class };
|
|
8
8
|
|
|
9
|
-
function setInMap<T>(map: Map<Class, Map<typeof key, T>>,
|
|
10
|
-
if (!map.has(
|
|
11
|
-
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());
|
|
12
12
|
}
|
|
13
|
-
map.get(
|
|
13
|
+
map.get(cls)!.set(key, dest);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class DependencyRegistryResolver {
|
|
@@ -38,15 +38,17 @@ export class DependencyRegistryResolver {
|
|
|
38
38
|
if (qualifiers.has(PrimaryCandidateSymbol)) {
|
|
39
39
|
return PrimaryCandidateSymbol;
|
|
40
40
|
} else {
|
|
41
|
-
const filtered = resolved
|
|
41
|
+
const filtered = resolved
|
|
42
|
+
.filter(qualifier => !!qualifier)
|
|
43
|
+
.filter(qualifier => this.#defaultSymbols.has(qualifier));
|
|
42
44
|
// If there is only one default symbol
|
|
43
45
|
if (filtered.length === 1) {
|
|
44
46
|
return filtered[0];
|
|
45
47
|
} else if (filtered.length > 1) {
|
|
46
48
|
// If dealing with sub types, prioritize exact matches
|
|
47
49
|
const exact = this.getCandidateEntries(type)
|
|
48
|
-
.map(([_,
|
|
49
|
-
.filter(
|
|
50
|
+
.map(([_, candidate]) => candidate)
|
|
51
|
+
.filter(candidate => candidate.candidateType === type);
|
|
50
52
|
|
|
51
53
|
if (exact.length === 1) {
|
|
52
54
|
return exact[0].qualifier;
|
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
|
};
|