From 260470b766d5fdb323c1bd72cc6260a90468a161 Mon Sep 17 00:00:00 2001 From: Gxkl Date: Mon, 28 Oct 2024 18:50:19 +0800 Subject: [PATCH] feat: support optional inject (#254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ##### Checklist - [x] `npm test` passes - [x] tests and/or benchmarks are included - [x] documentation is changed or added - [x] commit message follows commit guidelines ##### Affected core subsystem(s) ##### Description of change ## Summary by CodeRabbit ## Release Notes - **New Features** - Introduced optional dependency injection capabilities, allowing services to be injected as optional. - Added a new `InjectOptional` decorator for marking parameters as optional. - **Improvements** - Enhanced error handling in the `EggPrototypeBuilder` for optional inject objects. - Streamlined logic for parameter handling in the `Inject` decorator. - Improved flexibility in service management by allowing optional dependencies in various services. - **Tests** - Expanded test coverage for optional injections in various scenarios. - Added new test cases to validate the functionality of optional dependencies. - **Documentation** - Updated documentation to include detailed sections on optional dependency injection and lifecycle hooks, enhancing clarity and usability. --- README.md | 37 +++++++++++- core/core-decorator/src/decorator/Inject.ts | 28 +++++++-- core/core-decorator/test/decorators.test.ts | 10 ++++ .../test/fixtures/decators/CacheService.ts | 9 ++- .../fixtures/decators/ConstructorObject.ts | 4 +- core/metadata/src/impl/EggPrototypeBuilder.ts | 52 +++++++++------- core/metadata/test/LoadUnit.test.ts | 12 ++++ .../OptionalInjectService.ts | 13 ++++ .../optional-inject-module/package.json | 6 ++ core/types/core-decorator/Inject.ts | 2 + .../model/InjectConstructorInfo.ts | 4 ++ .../core-decorator/model/InjectObjectInfo.ts | 4 ++ core/types/metadata/model/EggPrototype.ts | 16 +++++ plugin/tegg/test/Inject.test.ts | 60 +++++++++++++++++++ .../app/modules/module-a/BarService.ts | 11 ++++ .../app/modules/module-a/package.json | 6 ++ .../invalid-inject/config/config.default.js | 20 +++++++ .../apps/invalid-inject/config/plugin.js | 13 ++++ .../fixtures/apps/invalid-inject/package.json | 3 + .../app/modules/module-a/BarService.ts | 17 ++++++ .../app/modules/module-a/FooService.ts | 16 +++++ .../app/modules/module-a/package.json | 6 ++ .../optional-inject/config/config.default.js | 20 +++++++ .../apps/optional-inject/config/plugin.js | 13 ++++ .../apps/optional-inject/package.json | 3 + .../test/fixtures/invalid-inject/foo.ts | 13 ++++ .../test/fixtures/invalid-inject/package.json | 6 ++ .../test/fixtures/optional-inject/bar.ts | 10 ++++ .../test/fixtures/optional-inject/foo.ts | 20 +++++++ .../fixtures/optional-inject/package.json | 6 ++ standalone/standalone/test/index.test.ts | 18 ++++++ 31 files changed, 429 insertions(+), 29 deletions(-) create mode 100644 core/metadata/test/fixtures/modules/optional-inject-module/OptionalInjectService.ts create mode 100644 core/metadata/test/fixtures/modules/optional-inject-module/package.json create mode 100644 plugin/tegg/test/Inject.test.ts create mode 100644 plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/BarService.ts create mode 100644 plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/package.json create mode 100644 plugin/tegg/test/fixtures/apps/invalid-inject/config/config.default.js create mode 100644 plugin/tegg/test/fixtures/apps/invalid-inject/config/plugin.js create mode 100644 plugin/tegg/test/fixtures/apps/invalid-inject/package.json create mode 100644 plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/BarService.ts create mode 100644 plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/FooService.ts create mode 100644 plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/package.json create mode 100644 plugin/tegg/test/fixtures/apps/optional-inject/config/config.default.js create mode 100644 plugin/tegg/test/fixtures/apps/optional-inject/config/plugin.js create mode 100644 plugin/tegg/test/fixtures/apps/optional-inject/package.json create mode 100644 standalone/standalone/test/fixtures/invalid-inject/foo.ts create mode 100644 standalone/standalone/test/fixtures/invalid-inject/package.json create mode 100644 standalone/standalone/test/fixtures/optional-inject/bar.ts create mode 100644 standalone/standalone/test/fixtures/optional-inject/foo.ts create mode 100644 standalone/standalone/test/fixtures/optional-inject/package.json diff --git a/README.md b/README.md index 05366fb2..f95d396d 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,25 @@ Proto 中可以依赖其他的 Proto,或者 egg 中的对象。 // 在某些情况不希望注入的原型和属性使用一个名称 // 默认为属性名称 proto?: string; + // 注入对象是否为可选,默认为 false + // 若为 false,当不存在该对象时,启动阶段将会抛出异常 + // 若为 true,且未找到对象时,该属性值为 undefined + optional?: boolean; +}) +``` + +对于 optional 为 true 的情况,也提供了 InjectOptional 的 alias 装饰器 +```typescript +// 等价于 @Inject({ ...params, optional: true }) +@InjectOptional(params: { + // 注入对象的名称,在某些情况下一个原型可能有多个实例 + // 比如说 egg 的 logger + // 默认为属性名称 + name?: string; + // 注入原型的名称 + // 在某些情况不希望注入的原型和属性使用一个名称 + // 默认为属性名称 + proto?: string; }) ``` @@ -489,9 +508,17 @@ import { Inject } from '@eggjs/tegg'; export class HelloService { @Inject() logger: EggLogger; + + // 等价于 @Inject({ optional: true }) + @InjectOptional() + maybeUndefinedLogger?: EggLogger; async hello(user: User): Promise { this.logger.info(`[HelloService] hello ${user.name}`); + // optional inject 使用时,需要判断是否有值 + if (this.maybeUndefinedLogger) { + this.maybeUndefinedLogger.info(`[HelloService] hello ${user.name}`); + } const echoResponse = await this.echoAdapter.echo({ name: user.name }); return `hello, ${echoResponse.name}`; } @@ -506,11 +533,17 @@ import { Inject } from '@eggjs/tegg'; @ContextProto() export class HelloService { - constructor(@Inject() readonly logger: EggLogger) { - } + constructor( + @Inject() readonly logger: EggLogger, + @InjectOptional() readonly maybeUndefinedLogger?: EggLogger, + ) {} async hello(user: User): Promise { this.logger.info(`[HelloService] hello ${user.name}`); + // optional inject 使用时,需要判断是否有值 + if (this.maybeUndefinedLogger) { + this.maybeUndefinedLogger.info(`[HelloService] hello ${user.name}`); + } const echoResponse = await this.echoAdapter.echo({ name: user.name }); return `hello, ${echoResponse.name}`; } diff --git a/core/core-decorator/src/decorator/Inject.ts b/core/core-decorator/src/decorator/Inject.ts index 8dbef837..e9275299 100644 --- a/core/core-decorator/src/decorator/Inject.ts +++ b/core/core-decorator/src/decorator/Inject.ts @@ -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) { @@ -15,7 +17,7 @@ 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 = { @@ -23,6 +25,10 @@ export function Inject(param?: InjectParams | string) { objName: objName || propertyKey, }; + if (injectParam?.optional) { + injectObject.optional = true; + } + PrototypeUtil.setInjectType(target.constructor, InjectType.PROPERTY); PrototypeUtil.addInjectObject(target.constructor as EggProtoImplClass, injectObject); } @@ -30,14 +36,17 @@ export function Inject(param?: InjectParams | string) { 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); } @@ -50,3 +59,12 @@ export function Inject(param?: InjectParams | string) { } }; } + +export function InjectOptional(param?: Omit | string) { + const injectParam = typeof param === 'string' ? { name: param } : param; + + return Inject({ + ...injectParam, + optional: true, + }); +} diff --git a/core/core-decorator/test/decorators.test.ts b/core/core-decorator/test/decorators.test.ts index c88072d5..670603a3 100644 --- a/core/core-decorator/test/decorators.test.ts +++ b/core/core-decorator/test/decorators.test.ts @@ -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); }); @@ -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 }, ]); }); }); diff --git a/core/core-decorator/test/fixtures/decators/CacheService.ts b/core/core-decorator/test/fixtures/decators/CacheService.ts index 5703d6ec..503a010b 100644 --- a/core/core-decorator/test/fixtures/decators/CacheService.ts +++ b/core/core-decorator/test/fixtures/decators/CacheService.ts @@ -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'; @@ -37,4 +38,10 @@ export default class CacheService { @Inject() testService4: any; + + @Inject({ optional: true }) + optionalService1?: any; + + @InjectOptional() + optionalService2?: any; } diff --git a/core/core-decorator/test/fixtures/decators/ConstructorObject.ts b/core/core-decorator/test/fixtures/decators/ConstructorObject.ts index 49768dd3..2d48a559 100644 --- a/core/core-decorator/test/fixtures/decators/ConstructorObject.ts +++ b/core/core-decorator/test/fixtures/decators/ConstructorObject.ts @@ -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'; @@ -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, ) { } } diff --git a/core/metadata/src/impl/EggPrototypeBuilder.ts b/core/metadata/src/impl/EggPrototypeBuilder.ts index c74da8a7..8da11612 100644 --- a/core/metadata/src/impl/EggPrototypeBuilder.ts +++ b/core/metadata/src/impl/EggPrototypeBuilder.ts @@ -63,7 +63,7 @@ export class EggPrototypeBuilder { return builder.build(); } - private tryFindDefaultPrototype(injectObject: InjectObject): EggPrototype { + private tryFindDefaultPrototype(injectObject: InjectObject | InjectConstructor): EggPrototype { const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName); const multiInstancePropertyQualifiers = this.properQualifiers[injectObject.refName as string] ?? []; return EggPrototypeFactory.instance.getPrototype(injectObject.objName, this.loadUnit, QualifierUtil.mergeQualifiers( @@ -72,7 +72,7 @@ export class EggPrototypeBuilder { )); } - private tryFindContextPrototype(injectObject: InjectObject): EggPrototype { + private tryFindContextPrototype(injectObject: InjectObject | InjectConstructor): EggPrototype { const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName); const multiInstancePropertyQualifiers = this.properQualifiers[injectObject.refName as string] ?? []; return EggPrototypeFactory.instance.getPrototype(injectObject.objName, this.loadUnit, QualifierUtil.mergeQualifiers( @@ -85,7 +85,7 @@ export class EggPrototypeBuilder { )); } - private tryFindSelfInitTypePrototype(injectObject: InjectObject): EggPrototype { + private tryFindSelfInitTypePrototype(injectObject: InjectObject | InjectConstructor): EggPrototype { const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName); const multiInstancePropertyQualifiers = this.properQualifiers[injectObject.refName as string] ?? []; return EggPrototypeFactory.instance.getPrototype(injectObject.objName, this.loadUnit, QualifierUtil.mergeQualifiers( @@ -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); @@ -121,22 +121,34 @@ export class EggPrototypeBuilder { const injectObjectProtos: Array = []; 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); diff --git a/core/metadata/test/LoadUnit.test.ts b/core/metadata/test/LoadUnit.test.ts index 65a24d6f..a7eb61a6 100644 --- a/core/metadata/test/LoadUnit.test.ts +++ b/core/metadata/test/LoadUnit.test.ts @@ -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'); diff --git a/core/metadata/test/fixtures/modules/optional-inject-module/OptionalInjectService.ts b/core/metadata/test/fixtures/modules/optional-inject-module/OptionalInjectService.ts new file mode 100644 index 00000000..1b8312d6 --- /dev/null +++ b/core/metadata/test/fixtures/modules/optional-inject-module/OptionalInjectService.ts @@ -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; +} diff --git a/core/metadata/test/fixtures/modules/optional-inject-module/package.json b/core/metadata/test/fixtures/modules/optional-inject-module/package.json new file mode 100644 index 00000000..84211083 --- /dev/null +++ b/core/metadata/test/fixtures/modules/optional-inject-module/package.json @@ -0,0 +1,6 @@ +{ + "name": "optional-inject-service", + "eggModule": { + "name": "optionalInjectService" + } +} diff --git a/core/types/core-decorator/Inject.ts b/core/types/core-decorator/Inject.ts index 85513bb5..d310d48d 100644 --- a/core/types/core-decorator/Inject.ts +++ b/core/types/core-decorator/Inject.ts @@ -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; } diff --git a/core/types/core-decorator/model/InjectConstructorInfo.ts b/core/types/core-decorator/model/InjectConstructorInfo.ts index 2b58471c..6919702f 100644 --- a/core/types/core-decorator/model/InjectConstructorInfo.ts +++ b/core/types/core-decorator/model/InjectConstructorInfo.ts @@ -13,4 +13,8 @@ export interface InjectConstructorInfo { * obj's name will be injected */ objName: EggObjectName; + /** + * optional inject + */ + optional?: boolean; } diff --git a/core/types/core-decorator/model/InjectObjectInfo.ts b/core/types/core-decorator/model/InjectObjectInfo.ts index d83d122c..289bc4ae 100644 --- a/core/types/core-decorator/model/InjectObjectInfo.ts +++ b/core/types/core-decorator/model/InjectObjectInfo.ts @@ -9,4 +9,8 @@ export interface InjectObjectInfo { * obj's name will be injected */ objName: EggObjectName; + /** + * optional inject + */ + optional?: boolean; } diff --git a/core/types/metadata/model/EggPrototype.ts b/core/types/metadata/model/EggPrototype.ts index a3ad62d7..361ca32f 100644 --- a/core/types/metadata/model/EggPrototype.ts +++ b/core/types/metadata/model/EggPrototype.ts @@ -25,6 +25,10 @@ export interface InjectObjectProto { * inject qualifiers */ qualifiers: QualifierInfo[]; + /** + * optional inject + */ + optional?: boolean; /** * inject prototype */ @@ -48,6 +52,10 @@ export interface InjectConstructorProto { * inject qualifiers */ qualifiers: QualifierInfo[]; + /** + * optional inject + */ + optional?: boolean; /** * inject prototype */ @@ -68,6 +76,10 @@ export interface InjectObject { * if null same as current obj */ initType?: ObjectInitTypeLike; + /** + * optional inject + */ + optional?: boolean; } export interface InjectConstructor { @@ -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; diff --git a/plugin/tegg/test/Inject.test.ts b/plugin/tegg/test/Inject.test.ts new file mode 100644 index 00000000..f059bc6b --- /dev/null +++ b/plugin/tegg/test/Inject.test.ts @@ -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/, + ); + }); +}); diff --git a/plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/BarService.ts b/plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/BarService.ts new file mode 100644 index 00000000..e17acb4d --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/BarService.ts @@ -0,0 +1,11 @@ +import { SingletonProto, Inject } from '@eggjs/tegg'; + +@SingletonProto() +export class BarService { + @Inject() + doesNotExist: object; + + bar() { + console.log(this.doesNotExist); + } +} diff --git a/plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/package.json b/plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/package.json new file mode 100644 index 00000000..0d34fd6f --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/package.json @@ -0,0 +1,6 @@ +{ + "name": "module-a", + "eggModule": { + "name": "a" + } +} diff --git a/plugin/tegg/test/fixtures/apps/invalid-inject/config/config.default.js b/plugin/tegg/test/fixtures/apps/invalid-inject/config/config.default.js new file mode 100644 index 00000000..48e52956 --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/invalid-inject/config/config.default.js @@ -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; +}; diff --git a/plugin/tegg/test/fixtures/apps/invalid-inject/config/plugin.js b/plugin/tegg/test/fixtures/apps/invalid-inject/config/plugin.js new file mode 100644 index 00000000..10d5c293 --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/invalid-inject/config/plugin.js @@ -0,0 +1,13 @@ +'use strict'; + +exports.tracer = { + package: 'egg-tracer', + enable: true, +}; + +exports.teggConfig = { + package: '@eggjs/tegg-config', + enable: true, +}; + +exports.watcher = false; diff --git a/plugin/tegg/test/fixtures/apps/invalid-inject/package.json b/plugin/tegg/test/fixtures/apps/invalid-inject/package.json new file mode 100644 index 00000000..978d31f2 --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/invalid-inject/package.json @@ -0,0 +1,3 @@ +{ + "name": "egg-app" +} diff --git a/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/BarService.ts b/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/BarService.ts new file mode 100644 index 00000000..fbfc0d8c --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/BarService.ts @@ -0,0 +1,17 @@ +import { SingletonProto, Inject, InjectOptional } from '@eggjs/tegg'; + +@SingletonProto() +export class BarService { + @Inject({ optional: true }) + doesNotExist1?: object; + + @InjectOptional() + doesNotExist2?: object; + + bar() { + return { + nil1: this.doesNotExist1 ? 'N' : 'Y', + nil2: this.doesNotExist2 ? 'N' : 'Y', + }; + } +} diff --git a/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/FooService.ts b/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/FooService.ts new file mode 100644 index 00000000..da760316 --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/FooService.ts @@ -0,0 +1,16 @@ +import { SingletonProto, Inject, InjectOptional } from '@eggjs/tegg'; + +@SingletonProto() +export class FooService { + constructor( + @Inject({ optional: true }) readonly doesNotExist1?: object, + @InjectOptional() readonly doesNotExist2?: object, + ) {} + + foo() { + return { + nil1: this.doesNotExist1 ? 'N' : 'Y', + nil2: this.doesNotExist2 ? 'N' : 'Y', + }; + } +} diff --git a/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/package.json b/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/package.json new file mode 100644 index 00000000..0d34fd6f --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/package.json @@ -0,0 +1,6 @@ +{ + "name": "module-a", + "eggModule": { + "name": "a" + } +} diff --git a/plugin/tegg/test/fixtures/apps/optional-inject/config/config.default.js b/plugin/tegg/test/fixtures/apps/optional-inject/config/config.default.js new file mode 100644 index 00000000..48e52956 --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/optional-inject/config/config.default.js @@ -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; +}; diff --git a/plugin/tegg/test/fixtures/apps/optional-inject/config/plugin.js b/plugin/tegg/test/fixtures/apps/optional-inject/config/plugin.js new file mode 100644 index 00000000..10d5c293 --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/optional-inject/config/plugin.js @@ -0,0 +1,13 @@ +'use strict'; + +exports.tracer = { + package: 'egg-tracer', + enable: true, +}; + +exports.teggConfig = { + package: '@eggjs/tegg-config', + enable: true, +}; + +exports.watcher = false; diff --git a/plugin/tegg/test/fixtures/apps/optional-inject/package.json b/plugin/tegg/test/fixtures/apps/optional-inject/package.json new file mode 100644 index 00000000..978d31f2 --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/optional-inject/package.json @@ -0,0 +1,3 @@ +{ + "name": "egg-app" +} diff --git a/standalone/standalone/test/fixtures/invalid-inject/foo.ts b/standalone/standalone/test/fixtures/invalid-inject/foo.ts new file mode 100644 index 00000000..654bbff7 --- /dev/null +++ b/standalone/standalone/test/fixtures/invalid-inject/foo.ts @@ -0,0 +1,13 @@ +import { Inject, SingletonProto } from '@eggjs/tegg'; +import { MainRunner, Runner } from '@eggjs/tegg/standalone'; + +@Runner() +@SingletonProto() +export class Foo implements MainRunner { + @Inject() + doesNotExist?: object; + + async main(): Promise { + return !!this.doesNotExist; + } +} diff --git a/standalone/standalone/test/fixtures/invalid-inject/package.json b/standalone/standalone/test/fixtures/invalid-inject/package.json new file mode 100644 index 00000000..8ea29f49 --- /dev/null +++ b/standalone/standalone/test/fixtures/invalid-inject/package.json @@ -0,0 +1,6 @@ +{ + "name": "invalid-inject", + "eggModule": { + "name": "invalidInject" + } +} diff --git a/standalone/standalone/test/fixtures/optional-inject/bar.ts b/standalone/standalone/test/fixtures/optional-inject/bar.ts new file mode 100644 index 00000000..36304f4d --- /dev/null +++ b/standalone/standalone/test/fixtures/optional-inject/bar.ts @@ -0,0 +1,10 @@ +import { Inject, SingletonProto } from '@eggjs/tegg'; +import { InjectOptional } from '@eggjs/core-decorator'; + +@SingletonProto() +export class Bar { + constructor( + @InjectOptional() readonly hello?: object, + @Inject({ optional: true }) readonly world?: object, + ) {} +} diff --git a/standalone/standalone/test/fixtures/optional-inject/foo.ts b/standalone/standalone/test/fixtures/optional-inject/foo.ts new file mode 100644 index 00000000..5cb9ffd2 --- /dev/null +++ b/standalone/standalone/test/fixtures/optional-inject/foo.ts @@ -0,0 +1,20 @@ +import { Inject, InjectOptional, SingletonProto } from '@eggjs/tegg'; +import { Runner, MainRunner } from '@eggjs/tegg/standalone'; +import { Bar } from './bar'; + +@Runner() +@SingletonProto() +export class Foo implements MainRunner { + @Inject({ optional: true }) + hello?: object; + + @InjectOptional() + world?: object; + + @Inject() + bar: Bar; + + async main(): Promise { + return !this.hello && !this.world && !this.bar.hello && !this.bar.world; + } +} diff --git a/standalone/standalone/test/fixtures/optional-inject/package.json b/standalone/standalone/test/fixtures/optional-inject/package.json new file mode 100644 index 00000000..e23cef91 --- /dev/null +++ b/standalone/standalone/test/fixtures/optional-inject/package.json @@ -0,0 +1,6 @@ +{ + "name": "optional-inject", + "eggModule": { + "name": "optionalInject" + } +} diff --git a/standalone/standalone/test/index.test.ts b/standalone/standalone/test/index.test.ts index 48802f65..beefca8f 100644 --- a/standalone/standalone/test/index.test.ts +++ b/standalone/standalone/test/index.test.ts @@ -185,6 +185,24 @@ describe('standalone/standalone/test/index.test.ts', () => { }); }); + describe('inject', () => { + it('should optional work', async () => { + const fixturePath = path.join(__dirname, './fixtures/optional-inject'); + const nil = await main(fixturePath); + assert.equal(nil, true); + }); + + it('should throw error if no proto found', async () => { + const fixturePath = path.join(__dirname, './fixtures/invalid-inject'); + const runner = new Runner(fixturePath); + await assert.rejects( + runner.init(), + /EggPrototypeNotFound: Object doesNotExist not found in LOAD_UNIT:invalidInject/, + ); + await runner.destroy(); + }); + }); + describe('aop runtime', () => { const fixturePath = path.join(__dirname, './fixtures/aop-module');