Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 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,29 +17,36 @@ 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);
}

function constructorInject(target: any, parameterIndex: number) {
const argNames = ObjectUtils.getConstructorArgNameList(target);
const argName = argNames[parameterIndex];
// TODO get objName from design:type
const objName = typeof param === 'string' ? param : param?.name;
const injectObject: InjectConstructorInfo = {
refIndex: parameterIndex,
refName: argName,
objName: objName || argName,
// TODO get objName from design:type
objName: injectParam?.name || argName,
};

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

PrototypeUtil.setInjectType(target, InjectType.CONSTRUCTOR);
PrototypeUtil.addInjectConstructor(target as EggProtoImplClass, injectObject);
}
Expand All @@ -50,3 +59,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', optional: true },
{ refIndex: 3, refName: 'optional2', objName: 'optional2', optional: true },
]);
});
});
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/InjectConstructorInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ export interface InjectConstructorInfo {
* obj's name will be injected
*/
objName: EggObjectName;
/**
* optional inject
*/
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
60 changes: 60 additions & 0 deletions plugin/tegg/test/Inject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import mm from 'egg-mock';
import path from 'path';
import assert from 'assert';
import { BarService } from './fixtures/apps/optional-inject/app/modules/module-a/BarService';
import { FooService } from './fixtures/apps/optional-inject/app/modules/module-a/FooService';

describe('plugin/tegg/test/Inject.test.ts', () => {
let app;

beforeEach(async () => {
mm(process.env, 'EGG_TYPESCRIPT', true);
mm(process, 'cwd', () => {
return path.join(__dirname, '..');
});
});

afterEach(async () => {
await app.close();
mm.restore();
});

describe('optional', () => {
beforeEach(async () => {
app = mm.app({
baseDir: path.join(__dirname, 'fixtures/apps/optional-inject'),
framework: require.resolve('egg'),
});
await app.ready();
});

it('should work with property', async () => {
const barService: BarService = await app.getEggObject(BarService);
const res = barService.bar();
assert.deepStrictEqual(res, {
nil1: 'Y',
nil2: 'Y',
});
});

it('should work with constructor', async () => {
const fooService: FooService = await app.getEggObject(FooService);
const res = fooService.foo();
assert.deepStrictEqual(res, {
nil1: 'Y',
nil2: 'Y',
});
});
});

it('should throw error if no proto found', async () => {
app = mm.app({
baseDir: path.join(__dirname, 'fixtures/apps/invalid-inject'),
framework: require.resolve('egg'),
});
await assert.rejects(
app.ready(),
/EggPrototypeNotFound: Object doesNotExist not found in LOAD_UNIT:a/,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SingletonProto, Inject } from '@eggjs/tegg';

@SingletonProto()
export class BarService {
@Inject()
doesNotExist: object;

bar() {
console.log(this.doesNotExist);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "module-a",
"eggModule": {
"name": "a"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

const path = require('path');

module.exports = function(appInfo) {
const config = {
keys: 'test key',
customLogger: {
xxLogger: {
file: path.join(appInfo.root, 'logs/xx.log'),
},
},
security: {
csrf: {
ignoreJSON: false,
},
},
};
return config;
};
Loading
Loading