Skip to content

Commit

Permalink
feat: support optional inject
Browse files Browse the repository at this point in the history
  • Loading branch information
gxkl committed Oct 27, 2024
1 parent 3604a40 commit 8a45b8f
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 21 deletions.
19 changes: 17 additions & 2 deletions core/core-decorator/src/decorator/Inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { PrototypeUtil } from '../util/PrototypeUtil';
import { ObjectUtils } from '@eggjs/tegg-common-util';

export function Inject(param?: InjectParams | string) {
const injectParam = typeof param === 'string' ? { name: param } : param;

function propertyInject(target: any, propertyKey: PropertyKey) {
let objName: PropertyKey | undefined;
if (!param) {
if (!injectParam) {
// try to read design:type from proto
const proto = PrototypeUtil.getDesignType(target, propertyKey);
if (typeof proto === 'function' && proto !== Object) {
Expand All @@ -15,14 +17,18 @@ export function Inject(param?: InjectParams | string) {
}
} else {
// params allow string or object
objName = typeof param === 'string' ? param : param?.name;
objName = injectParam?.name;
}

const injectObject: InjectObjectInfo = {
refName: propertyKey,
objName: objName || propertyKey,
};

if (injectParam?.optional) {
injectObject.optional = true;
}

PrototypeUtil.setInjectType(target.constructor, InjectType.PROPERTY);
PrototypeUtil.addInjectObject(target.constructor as EggProtoImplClass, injectObject);
}
Expand Down Expand Up @@ -50,3 +56,12 @@ export function Inject(param?: InjectParams | string) {
}
};
}

export function InjectOptional(param?: Omit<InjectParams, 'optional'> | string) {
const injectParam = typeof param === 'string' ? { name: param } : param;

return Inject({
...injectParam,
optional: true,
});
}
10 changes: 10 additions & 0 deletions core/core-decorator/test/decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ describe('test/decorator.test.ts', () => {
}, {
objName: 'testService4',
refName: 'testService4',
}, {
objName: 'optionalService1',
refName: 'optionalService1',
optional: true,
}, {
objName: 'optionalService2',
refName: 'optionalService2',
optional: true,
}];
assert.deepStrictEqual(PrototypeUtil.getInjectObjects(CacheService), expectInjectInfo);
});
Expand All @@ -82,6 +90,8 @@ describe('test/decorator.test.ts', () => {
assert.deepStrictEqual(injectConstructors, [
{ refIndex: 0, refName: 'xCache', objName: 'fooCache' },
{ refIndex: 1, refName: 'cache', objName: 'cache' },
{ refIndex: 2, refName: 'optional1', objName: 'optional1' },
{ refIndex: 3, refName: 'optional2', objName: 'optional2' },
]);
});
});
Expand Down
9 changes: 8 additions & 1 deletion core/core-decorator/test/fixtures/decators/CacheService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ContextProto, Inject } from '../../..';
import { ContextProto } from '../../../src/decorator/ContextProto';
import { Inject, InjectOptional } from '../../../src/decorator/Inject';
import { ICache } from './ICache';
import { TestService, TestService2 } from './OtherService';

Expand Down Expand Up @@ -37,4 +38,10 @@ export default class CacheService {

@Inject()
testService4: any;

@Inject({ optional: true })
optionalService1?: any;

@InjectOptional()
optionalService2?: any;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SingletonProto } from '../../../src/decorator/SingletonProto';
import { ICache } from './ICache';
import { Inject } from '../../../src/decorator/Inject';
import { Inject, InjectOptional } from '../../../src/decorator/Inject';
import { InitTypeQualifier } from '../../../src/decorator/InitTypeQualifier';
import { ObjectInitType } from '@eggjs/tegg-types';
import { ModuleQualifier } from '../../../src/decorator/ModuleQualifier';
Expand All @@ -12,6 +12,8 @@ export class ConstructorObject {
@ModuleQualifier('foo')
@Inject({ name: 'fooCache'}) readonly xCache: ICache,
@Inject() readonly cache: ICache,
@Inject({ optional: true }) readonly optional1?: ICache,
@InjectOptional() readonly optional2?: ICache,
) {
}
}
46 changes: 29 additions & 17 deletions core/metadata/src/impl/EggPrototypeBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class EggPrototypeBuilder {
));
}

private findInjectObjectPrototype(injectObject: InjectObject): EggPrototype {
private findInjectObjectPrototype(injectObject: InjectObject | InjectConstructor): EggPrototype {
const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);
try {
return this.tryFindDefaultPrototype(injectObject);
Expand All @@ -121,22 +121,34 @@ export class EggPrototypeBuilder {
const injectObjectProtos: Array<InjectObjectProto | InjectConstructorProto> = [];
for (const injectObject of this.injectObjects) {
const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);
const proto = this.findInjectObjectPrototype(injectObject);
if (this.injectType === InjectType.PROPERTY) {
injectObjectProtos.push({
refName: injectObject.refName,
objName: injectObject.objName,
qualifiers: propertyQualifiers,
proto,
});
} else {
injectObjectProtos.push({
refIndex: (injectObject as InjectConstructor).refIndex,
refName: injectObject.refName,
objName: injectObject.objName,
qualifiers: propertyQualifiers,
proto,
});
try {
const proto = this.findInjectObjectPrototype(injectObject);
let injectObjectProto: InjectObjectProto | InjectConstructorProto;
if (this.injectType === InjectType.PROPERTY) {
injectObjectProto = {
refName: injectObject.refName,
objName: injectObject.objName,
qualifiers: propertyQualifiers,
proto,
};
} else {
injectObjectProto = {
refIndex: (injectObject as InjectConstructor).refIndex,
refName: injectObject.refName,
objName: injectObject.objName,
qualifiers: propertyQualifiers,
proto,
};
}
if (injectObject.optional) {
injectObject.optional = true;
}
injectObjectProtos.push(injectObjectProto);
} catch (e) {
if (e instanceof EggPrototypeNotFound && injectObject.optional) {
continue;
}
throw e;
}
}
const id = IdenticalUtil.createProtoId(this.loadUnit.id, this.name);
Expand Down
12 changes: 12 additions & 0 deletions core/metadata/test/LoadUnit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ describe('test/LoadUnit/LoadUnit.test.ts', () => {
});
});

describe('optional inject', () => {
it('should success', async () => {
const optionalInjectModulePath = path.join(__dirname, './fixtures/modules/optional-inject-module');
const loader = new TestLoader(optionalInjectModulePath);
buildGlobalGraph([ optionalInjectModulePath ], [ loader ]);

const loadUnit = await LoadUnitFactory.createLoadUnit(optionalInjectModulePath, EggLoadUnitType.MODULE, loader);
const optionalInjectServiceProto = loadUnit.getEggPrototype('optionalInjectService', [{ attribute: InitTypeQualifierAttribute, value: ObjectInitType.SINGLETON }]);
assert.deepStrictEqual(optionalInjectServiceProto[0].injectObjects, []);
});
});

describe('invalidate load unit', () => {
it('should init failed', async () => {
const invalidateModulePath = path.join(__dirname, './fixtures/modules/invalidate-module');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Inject, InjectOptional, SingletonProto } from '@eggjs/core-decorator';

interface PersistenceService {
}

@SingletonProto()
export default class OptionalInjectService {
@Inject({ optional: true })
persistenceService: PersistenceService;

@InjectOptional()
persistenceService2: PersistenceService;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "optional-inject-service",
"eggModule": {
"name": "optionalInjectService"
}
}
2 changes: 2 additions & 0 deletions core/types/core-decorator/Inject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export interface InjectParams {
// obj instance name, default is property name
name?: string;
// optional inject, default is false which means it will throw error when there is no relative object
optional?: boolean;
}
4 changes: 4 additions & 0 deletions core/types/core-decorator/model/InjectObjectInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ export interface InjectObjectInfo {
* obj's name will be injected
*/
objName: EggObjectName;
/**
* optional inject
*/
optional?: boolean;
}
16 changes: 16 additions & 0 deletions core/types/metadata/model/EggPrototype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export interface InjectObjectProto {
* inject qualifiers
*/
qualifiers: QualifierInfo[];
/**
* optional inject
*/
optional?: boolean;
/**
* inject prototype
*/
Expand All @@ -48,6 +52,10 @@ export interface InjectConstructorProto {
* inject qualifiers
*/
qualifiers: QualifierInfo[];
/**
* optional inject
*/
optional?: boolean;
/**
* inject prototype
*/
Expand All @@ -68,6 +76,10 @@ export interface InjectObject {
* if null same as current obj
*/
initType?: ObjectInitTypeLike;
/**
* optional inject
*/
optional?: boolean;
}

export interface InjectConstructor {
Expand All @@ -88,6 +100,10 @@ export interface InjectConstructor {
* if null same as current obj
*/
initType?: ObjectInitTypeLike;
/**
* optional inject
*/
optional?: boolean;
}

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

0 comments on commit 8a45b8f

Please sign in to comment.