Skip to content

Commit 8a45b8f

Browse files
committed
feat: support optional inject
1 parent 3604a40 commit 8a45b8f

File tree

11 files changed

+120
-21
lines changed

11 files changed

+120
-21
lines changed

core/core-decorator/src/decorator/Inject.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { PrototypeUtil } from '../util/PrototypeUtil';
33
import { ObjectUtils } from '@eggjs/tegg-common-util';
44

55
export function Inject(param?: InjectParams | string) {
6+
const injectParam = typeof param === 'string' ? { name: param } : param;
7+
68
function propertyInject(target: any, propertyKey: PropertyKey) {
79
let objName: PropertyKey | undefined;
8-
if (!param) {
10+
if (!injectParam) {
911
// try to read design:type from proto
1012
const proto = PrototypeUtil.getDesignType(target, propertyKey);
1113
if (typeof proto === 'function' && proto !== Object) {
@@ -15,14 +17,18 @@ export function Inject(param?: InjectParams | string) {
1517
}
1618
} else {
1719
// params allow string or object
18-
objName = typeof param === 'string' ? param : param?.name;
20+
objName = injectParam?.name;
1921
}
2022

2123
const injectObject: InjectObjectInfo = {
2224
refName: propertyKey,
2325
objName: objName || propertyKey,
2426
};
2527

28+
if (injectParam?.optional) {
29+
injectObject.optional = true;
30+
}
31+
2632
PrototypeUtil.setInjectType(target.constructor, InjectType.PROPERTY);
2733
PrototypeUtil.addInjectObject(target.constructor as EggProtoImplClass, injectObject);
2834
}
@@ -50,3 +56,12 @@ export function Inject(param?: InjectParams | string) {
5056
}
5157
};
5258
}
59+
60+
export function InjectOptional(param?: Omit<InjectParams, 'optional'> | string) {
61+
const injectParam = typeof param === 'string' ? { name: param } : param;
62+
63+
return Inject({
64+
...injectParam,
65+
optional: true,
66+
});
67+
}

core/core-decorator/test/decorators.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ describe('test/decorator.test.ts', () => {
7373
}, {
7474
objName: 'testService4',
7575
refName: 'testService4',
76+
}, {
77+
objName: 'optionalService1',
78+
refName: 'optionalService1',
79+
optional: true,
80+
}, {
81+
objName: 'optionalService2',
82+
refName: 'optionalService2',
83+
optional: true,
7684
}];
7785
assert.deepStrictEqual(PrototypeUtil.getInjectObjects(CacheService), expectInjectInfo);
7886
});
@@ -82,6 +90,8 @@ describe('test/decorator.test.ts', () => {
8290
assert.deepStrictEqual(injectConstructors, [
8391
{ refIndex: 0, refName: 'xCache', objName: 'fooCache' },
8492
{ refIndex: 1, refName: 'cache', objName: 'cache' },
93+
{ refIndex: 2, refName: 'optional1', objName: 'optional1' },
94+
{ refIndex: 3, refName: 'optional2', objName: 'optional2' },
8595
]);
8696
});
8797
});

core/core-decorator/test/fixtures/decators/CacheService.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { ContextProto, Inject } from '../../..';
1+
import { ContextProto } from '../../../src/decorator/ContextProto';
2+
import { Inject, InjectOptional } from '../../../src/decorator/Inject';
23
import { ICache } from './ICache';
34
import { TestService, TestService2 } from './OtherService';
45

@@ -37,4 +38,10 @@ export default class CacheService {
3738

3839
@Inject()
3940
testService4: any;
41+
42+
@Inject({ optional: true })
43+
optionalService1?: any;
44+
45+
@InjectOptional()
46+
optionalService2?: any;
4047
}

core/core-decorator/test/fixtures/decators/ConstructorObject.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { SingletonProto } from '../../../src/decorator/SingletonProto';
22
import { ICache } from './ICache';
3-
import { Inject } from '../../../src/decorator/Inject';
3+
import { Inject, InjectOptional } from '../../../src/decorator/Inject';
44
import { InitTypeQualifier } from '../../../src/decorator/InitTypeQualifier';
55
import { ObjectInitType } from '@eggjs/tegg-types';
66
import { ModuleQualifier } from '../../../src/decorator/ModuleQualifier';
@@ -12,6 +12,8 @@ export class ConstructorObject {
1212
@ModuleQualifier('foo')
1313
@Inject({ name: 'fooCache'}) readonly xCache: ICache,
1414
@Inject() readonly cache: ICache,
15+
@Inject({ optional: true }) readonly optional1?: ICache,
16+
@InjectOptional() readonly optional2?: ICache,
1517
) {
1618
}
1719
}

core/metadata/src/impl/EggPrototypeBuilder.ts

+29-17
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class EggPrototypeBuilder {
9898
));
9999
}
100100

101-
private findInjectObjectPrototype(injectObject: InjectObject): EggPrototype {
101+
private findInjectObjectPrototype(injectObject: InjectObject | InjectConstructor): EggPrototype {
102102
const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);
103103
try {
104104
return this.tryFindDefaultPrototype(injectObject);
@@ -121,22 +121,34 @@ export class EggPrototypeBuilder {
121121
const injectObjectProtos: Array<InjectObjectProto | InjectConstructorProto> = [];
122122
for (const injectObject of this.injectObjects) {
123123
const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);
124-
const proto = this.findInjectObjectPrototype(injectObject);
125-
if (this.injectType === InjectType.PROPERTY) {
126-
injectObjectProtos.push({
127-
refName: injectObject.refName,
128-
objName: injectObject.objName,
129-
qualifiers: propertyQualifiers,
130-
proto,
131-
});
132-
} else {
133-
injectObjectProtos.push({
134-
refIndex: (injectObject as InjectConstructor).refIndex,
135-
refName: injectObject.refName,
136-
objName: injectObject.objName,
137-
qualifiers: propertyQualifiers,
138-
proto,
139-
});
124+
try {
125+
const proto = this.findInjectObjectPrototype(injectObject);
126+
let injectObjectProto: InjectObjectProto | InjectConstructorProto;
127+
if (this.injectType === InjectType.PROPERTY) {
128+
injectObjectProto = {
129+
refName: injectObject.refName,
130+
objName: injectObject.objName,
131+
qualifiers: propertyQualifiers,
132+
proto,
133+
};
134+
} else {
135+
injectObjectProto = {
136+
refIndex: (injectObject as InjectConstructor).refIndex,
137+
refName: injectObject.refName,
138+
objName: injectObject.objName,
139+
qualifiers: propertyQualifiers,
140+
proto,
141+
};
142+
}
143+
if (injectObject.optional) {
144+
injectObject.optional = true;
145+
}
146+
injectObjectProtos.push(injectObjectProto);
147+
} catch (e) {
148+
if (e instanceof EggPrototypeNotFound && injectObject.optional) {
149+
continue;
150+
}
151+
throw e;
140152
}
141153
}
142154
const id = IdenticalUtil.createProtoId(this.loadUnit.id, this.name);

core/metadata/test/LoadUnit.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ describe('test/LoadUnit/LoadUnit.test.ts', () => {
7575
});
7676
});
7777

78+
describe('optional inject', () => {
79+
it('should success', async () => {
80+
const optionalInjectModulePath = path.join(__dirname, './fixtures/modules/optional-inject-module');
81+
const loader = new TestLoader(optionalInjectModulePath);
82+
buildGlobalGraph([ optionalInjectModulePath ], [ loader ]);
83+
84+
const loadUnit = await LoadUnitFactory.createLoadUnit(optionalInjectModulePath, EggLoadUnitType.MODULE, loader);
85+
const optionalInjectServiceProto = loadUnit.getEggPrototype('optionalInjectService', [{ attribute: InitTypeQualifierAttribute, value: ObjectInitType.SINGLETON }]);
86+
assert.deepStrictEqual(optionalInjectServiceProto[0].injectObjects, []);
87+
});
88+
});
89+
7890
describe('invalidate load unit', () => {
7991
it('should init failed', async () => {
8092
const invalidateModulePath = path.join(__dirname, './fixtures/modules/invalidate-module');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Inject, InjectOptional, SingletonProto } from '@eggjs/core-decorator';
2+
3+
interface PersistenceService {
4+
}
5+
6+
@SingletonProto()
7+
export default class OptionalInjectService {
8+
@Inject({ optional: true })
9+
persistenceService: PersistenceService;
10+
11+
@InjectOptional()
12+
persistenceService2: PersistenceService;
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "optional-inject-service",
3+
"eggModule": {
4+
"name": "optionalInjectService"
5+
}
6+
}

core/types/core-decorator/Inject.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export interface InjectParams {
22
// obj instance name, default is property name
33
name?: string;
4+
// optional inject, default is false which means it will throw error when there is no relative object
5+
optional?: boolean;
46
}

core/types/core-decorator/model/InjectObjectInfo.ts

+4
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ export interface InjectObjectInfo {
99
* obj's name will be injected
1010
*/
1111
objName: EggObjectName;
12+
/**
13+
* optional inject
14+
*/
15+
optional?: boolean;
1216
}

core/types/metadata/model/EggPrototype.ts

+16
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export interface InjectObjectProto {
2525
* inject qualifiers
2626
*/
2727
qualifiers: QualifierInfo[];
28+
/**
29+
* optional inject
30+
*/
31+
optional?: boolean;
2832
/**
2933
* inject prototype
3034
*/
@@ -48,6 +52,10 @@ export interface InjectConstructorProto {
4852
* inject qualifiers
4953
*/
5054
qualifiers: QualifierInfo[];
55+
/**
56+
* optional inject
57+
*/
58+
optional?: boolean;
5159
/**
5260
* inject prototype
5361
*/
@@ -68,6 +76,10 @@ export interface InjectObject {
6876
* if null same as current obj
6977
*/
7078
initType?: ObjectInitTypeLike;
79+
/**
80+
* optional inject
81+
*/
82+
optional?: boolean;
7183
}
7284

7385
export interface InjectConstructor {
@@ -88,6 +100,10 @@ export interface InjectConstructor {
88100
* if null same as current obj
89101
*/
90102
initType?: ObjectInitTypeLike;
103+
/**
104+
* optional inject
105+
*/
106+
optional?: boolean;
91107
}
92108

93109
export type EggPrototypeClass = new (...args: any[]) => EggPrototype;

0 commit comments

Comments
 (0)