Skip to content

Commit

Permalink
feat: support inject in constructor (#237)
Browse files Browse the repository at this point in the history
<!--
Thank you for your pull request. Please review below requirements.
Bug fixes and new features should include tests and possibly benchmarks.
Contributors guide:
https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md

感谢您贡献代码。请确认下列 checklist 的完成情况。
Bug 修复和新功能必须包含测试,必要时请附上性能测试。
Contributors guide:
https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md
-->

##### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to
[x]. -->

- [ ] `npm test` passes
- [ ] tests and/or benchmarks are included
- [ ] documentation is changed or added
- [ ] commit message follows commit guidelines

##### Affected core subsystem(s)
<!-- Provide affected core subsystem(s). -->


##### Description of change
<!-- Provide a description of the change below this comment. -->

<!--
- any feature?
- close https://github.com/eggjs/egg/ISSUE_URL
-->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced new dependency injection methods and decorators, enhancing
flexibility in how dependencies are managed.
- Added a new example demonstrating the use of the `Inject` annotation
within the `HelloService` class.
- New `ConstructorObject` class created to showcase dependency injection
with various decorators.
- Added a static method for initializing array metadata in the
`MetadataUtil` class.

- **Bug Fixes**
- Improved error handling during object initialization to ensure better
management of dependencies and lifecycle events.

- **Tests**
- Expanded test coverage for decorators and injection functionality,
ensuring correct behavior of new features.
- Added new test cases for verifying constructor injection and qualifier
retrieval processes.

- **Documentation**
- Updated README.md with examples and explanations of new features and
usage patterns.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
killagu authored Sep 29, 2024
1 parent 6e13b26 commit e68b1ed
Show file tree
Hide file tree
Showing 45 changed files with 901 additions and 66 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,25 @@ export class HelloService {
}
```

也可在构造函数中使用 `Inject` 注解。注意 property 和 构造函数两种模式只能选一种,不能混用。

```typescript
import { EggLogger } from 'egg';
import { Inject } from '@eggjs/tegg';

@ContextProto()
export class HelloService {
constructor(@Inject() readonly logger: EggLogger) {
}

async hello(user: User): Promise<string> {
this.logger.info(`[HelloService] hello ${user.name}`);
const echoResponse = await this.echoAdapter.echo({ name: user.name });
return `hello, ${echoResponse.name}`;
}
}
```

##### 复杂示例

```typescript
Expand Down
17 changes: 17 additions & 0 deletions core/common-util/src/ObjectUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { EggProtoImplClass } from '@eggjs/tegg-types';

export class ObjectUtils {
static getProperties(obj: object): string[] {
const properties: string[] = [];
Expand Down Expand Up @@ -34,4 +36,19 @@ export class ObjectUtils {
}).filter(arg => arg.length);
return argNames;
}

static getConstructorArgNameList(clazz: EggProtoImplClass): string[] {
if (clazz.length === 0) {
return [];
}
const classString = clazz.toString();
const constructorMatch = classString.match(/constructor\s*\(([^)]+)\)/);
if (!constructorMatch) {
return [];
}
const params = constructorMatch[1].split(',').map(param => param.trim());
return params.map(param => param.match(/(\w+)\s*(?=\s*(?:=|\/\/|\s*$))/))
.filter(Boolean)
.map(match => match![0].trim());
}
}
41 changes: 41 additions & 0 deletions core/common-util/test/ObjectUtil.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import assert from 'node:assert';
import { ObjectUtils } from '..';

export function InitTypeQualifier() {
return function(_target: any, _propertyKey?: PropertyKey, _parameterIndex?: number) {
console.log(_target, _propertyKey, _parameterIndex);
// ...
};
}

export function ModuleQualifier(_foo: string) {
return function(_target: any, _propertyKey?: PropertyKey, _parameterIndex?: number) {
console.log(_target, _propertyKey, _parameterIndex, _foo);
// ...
};
}

export function Inject(_arg?: any) {
return function(_target: any, _propertyKey?: PropertyKey, _parameterIndex?: number) {
console.log(_target, _propertyKey, _parameterIndex, _arg);
// ...
};
}

describe('test/ObjectUtil.test.ts', () => {
it('should work', () => {
function mockFunction(/* test */ctx: object, foo: string, bar = '233') {
Expand All @@ -11,4 +32,24 @@ describe('test/ObjectUtil.test.ts', () => {
const argNames = ObjectUtils.getFunctionArgNameList(mockFunction);
assert.deepStrictEqual(argNames, [ 'ctx', 'foo', 'bar' ]);
});

it('getConstructorArgNameList should work', () => {
class ConstructorObject {
constructor(
@InitTypeQualifier()
@ModuleQualifier('foo')
@Inject({ name: 'fooCache' }) readonly xCache: any, // fpp...
/* test */ @Inject() readonly cache: unknown,
readonly v233 = 666,
) {
}
}

const argNames = ObjectUtils.getConstructorArgNameList(ConstructorObject);
assert.deepStrictEqual(argNames, [
'xCache',
'cache',
'v233',
]);
});
});
4 changes: 2 additions & 2 deletions core/core-decorator/src/decorator/ConfigSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ConfigSourceQualifierAttribute } from '@eggjs/tegg-types';
import type { EggProtoImplClass } from '@eggjs/tegg-types';

export function ConfigSourceQualifier(moduleName: string) {
return function(target: any, propertyKey: PropertyKey) {
QualifierUtil.addProperQualifier(target.constructor as EggProtoImplClass, propertyKey, ConfigSourceQualifierAttribute, moduleName);
return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
QualifierUtil.addInjectQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, ConfigSourceQualifierAttribute, moduleName);
};
}
4 changes: 2 additions & 2 deletions core/core-decorator/src/decorator/EggQualifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { EggProtoImplClass, EggType } from '@eggjs/tegg-types';
import { QualifierUtil } from '../util/QualifierUtil';

export function EggQualifier(eggType: EggType) {
return function(target: any, propertyKey: PropertyKey) {
QualifierUtil.addProperQualifier(target.constructor as EggProtoImplClass, propertyKey, EggQualifierAttribute, eggType);
return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
QualifierUtil.addInjectQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, EggQualifierAttribute, eggType);
};
}
4 changes: 2 additions & 2 deletions core/core-decorator/src/decorator/InitTypeQualifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { EggProtoImplClass, ObjectInitTypeLike } from '@eggjs/tegg-types';
import { QualifierUtil } from '../util/QualifierUtil';

export function InitTypeQualifier(initType: ObjectInitTypeLike) {
return function(target: any, propertyKey: PropertyKey) {
QualifierUtil.addProperQualifier(target.constructor as EggProtoImplClass, propertyKey, InitTypeQualifierAttribute, initType);
return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
QualifierUtil.addInjectQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, InitTypeQualifierAttribute, initType);
};
}
29 changes: 27 additions & 2 deletions core/core-decorator/src/decorator/Inject.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { EggProtoImplClass, InjectObjectInfo, InjectParams } from '@eggjs/tegg-types';
import { EggProtoImplClass, InjectObjectInfo, InjectConstructorInfo, InjectParams, InjectType } from '@eggjs/tegg-types';
import { PrototypeUtil } from '../util/PrototypeUtil';
import { ObjectUtils } from '@eggjs/tegg-common-util';

export function Inject(param?: InjectParams | string) {
return function(target: any, propertyKey: PropertyKey) {
function propertyInject(target: any, propertyKey: PropertyKey) {
let objName: PropertyKey | undefined;
if (!param) {
// try to read design:type from proto
Expand All @@ -22,6 +23,30 @@ export function Inject(param?: InjectParams | string) {
objName: objName || propertyKey,
};

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,
};

PrototypeUtil.setInjectType(target, InjectType.CONSTRUCTOR);
PrototypeUtil.addInjectConstructor(target as EggProtoImplClass, injectObject);
}

return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
if (typeof parameterIndex === 'undefined') {
propertyInject(target, propertyKey!);
} else {
constructorInject(target, parameterIndex!);
}
};
}
4 changes: 2 additions & 2 deletions core/core-decorator/src/decorator/ModuleQualifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { EggProtoImplClass } from '@eggjs/tegg-types';
import { QualifierUtil } from '../util/QualifierUtil';

export function ModuleQualifier(moduleName: string) {
return function(target: any, propertyKey: PropertyKey) {
QualifierUtil.addProperQualifier(target.constructor as EggProtoImplClass, propertyKey, LoadUnitNameQualifierAttribute, moduleName);
return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
QualifierUtil.addInjectQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, LoadUnitNameQualifierAttribute, moduleName);
};
}
14 changes: 14 additions & 0 deletions core/core-decorator/src/util/MetadataUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ export class MetadataUtil {
return this.getMetaData(metadataKey, clazz) || [];
}

/**
* init array metadata
* not inherit parent metadata
* return value true means use default value
* return value false means use map value
*/
static initArrayMetaData<T>(metadataKey: MetaDataKey, clazz: EggProtoImplClass, defaultValue: Array<T>): Array<T> {
const ownMetaData: Array<T> | undefined = this.getOwnMetaData(metadataKey, clazz);
if (!ownMetaData) {
this.defineMetaData(metadataKey, defaultValue, clazz);
}
return this.getOwnMetaData<Array<T>>(metadataKey, clazz)!;
}

/**
* init own array metadata
* if parent metadata exists, inherit
Expand Down
Loading

0 comments on commit e68b1ed

Please sign in to comment.