From 8ba4dcfcbf0d9911f8dc7084a1e043facb22dcd9 Mon Sep 17 00:00:00 2001 From: akitaSummer Date: Sun, 21 Dec 2025 02:13:48 +0800 Subject: [PATCH 1/2] fix: use FactoryQualifier --- core/dynamic-inject/index.ts | 2 ++ core/dynamic-inject/src/FactoryQualifier.ts | 8 +++++ .../src/FactoryQualifierUtil.ts | 31 +++++++++++++++++++ .../modules/dynamicRange/HelloService.ts | 21 +++++++++++++ core/dynamic-inject/test/typing.test.ts | 9 ++++++ core/runtime/src/impl/EggObjectImpl.ts | 27 +++++++++++++++- core/types/core-decorator/enum/Qualifier.ts | 2 ++ plugin/tegg/test/DynamicInject.test.ts | 17 ++++++++++ .../dynamic-inject-app/app/controller/app.ts | 6 ++++ .../apps/dynamic-inject-app/app/router.ts | 1 + .../FactoryQualifierService.ts | 23 ++++++++++++++ 11 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 core/dynamic-inject/src/FactoryQualifier.ts create mode 100644 core/dynamic-inject/src/FactoryQualifierUtil.ts create mode 100644 core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts create mode 100644 plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FactoryQualifierService.ts diff --git a/core/dynamic-inject/index.ts b/core/dynamic-inject/index.ts index 50a9b78e..e0a0f3c2 100644 --- a/core/dynamic-inject/index.ts +++ b/core/dynamic-inject/index.ts @@ -1,3 +1,5 @@ export * from '@eggjs/tegg-types/dynamic-inject'; export * from './src/QualifierImplUtil'; export * from './src/QualifierImplDecoratorUtil'; +export * from './src/FactoryQualifier'; +export * from './src/FactoryQualifierUtil'; diff --git a/core/dynamic-inject/src/FactoryQualifier.ts b/core/dynamic-inject/src/FactoryQualifier.ts new file mode 100644 index 00000000..9fa8ec5b --- /dev/null +++ b/core/dynamic-inject/src/FactoryQualifier.ts @@ -0,0 +1,8 @@ +import type { EggAbstractClazz, EggProtoImplClass } from '@eggjs/tegg-types'; +import { FactoryQualifierUtil } from './FactoryQualifierUtil'; + +export function FactoryQualifier(dynamics: EggAbstractClazz | EggAbstractClazz[]) { + return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) { + FactoryQualifierUtil.addProperQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, dynamics); + }; +} diff --git a/core/dynamic-inject/src/FactoryQualifierUtil.ts b/core/dynamic-inject/src/FactoryQualifierUtil.ts new file mode 100644 index 00000000..c8612466 --- /dev/null +++ b/core/dynamic-inject/src/FactoryQualifierUtil.ts @@ -0,0 +1,31 @@ +import { MetadataUtil } from '@eggjs/core-decorator'; +import { MapUtil, ObjectUtils } from '@eggjs/tegg-common-util'; +import { DYNAMIC_RANGE_META_DATA } from '@eggjs/tegg-types'; +import type { EggAbstractClazz, EggProtoImplClass } from '@eggjs/tegg-types'; + +export class FactoryQualifierUtil { + + static addProperQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, value: EggAbstractClazz | EggAbstractClazz[]) { + if (typeof parameterIndex === 'number') { + const argNames = ObjectUtils.getConstructorArgNameList(clazz); + const argName = argNames[parameterIndex]; + const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map()); + MapUtil.getOrStore(properQualifiers, argName, value); + } else { + const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, (clazz as any).constructor, new Map()); + MapUtil.getOrStore(properQualifiers, property, value); + } + } + + static getProperQualifiers(clazz: EggProtoImplClass, property: PropertyKey): EggAbstractClazz[] { + const properQualifiers: Map | undefined = MetadataUtil.getMetaData(DYNAMIC_RANGE_META_DATA, clazz); + const dynamics = properQualifiers?.get(property); + if (!dynamics) { + return []; + } + if (Array.isArray(dynamics)) { + return dynamics; + } + return [ dynamics ]; + } +} diff --git a/core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts b/core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts new file mode 100644 index 00000000..e8580487 --- /dev/null +++ b/core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts @@ -0,0 +1,21 @@ +import { ContextProto, Inject } from '@eggjs/core-decorator'; +import { EggObjectFactory } from '@eggjs/tegg-dynamic-inject'; +import { FactoryQualifier } from '../../../../src/FactoryQualifier'; +import { AbstractContextHello } from '../base/AbstractContextHello'; +import { ContextHelloType } from '../base/FooType'; + +@ContextProto() +export class HelloService { + @Inject() + @FactoryQualifier([ AbstractContextHello ]) + private readonly eggObjectFactory: EggObjectFactory; + + async hello(): Promise { + const helloImpls = await Promise.all([ + this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.FOO), + this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.BAR), + ]); + const msgs = helloImpls.map(helloImpl => helloImpl.hello()); + return msgs; + } +} diff --git a/core/dynamic-inject/test/typing.test.ts b/core/dynamic-inject/test/typing.test.ts index 1eb27c82..26cdce61 100644 --- a/core/dynamic-inject/test/typing.test.ts +++ b/core/dynamic-inject/test/typing.test.ts @@ -1,5 +1,9 @@ import path from 'path'; import coffee from 'coffee'; +import { HelloService } from './fixtures/modules/FactoryQualifier/HelloService'; +import { AbstractContextHello } from './fixtures/modules/base/AbstractContextHello'; +import { FactoryQualifierUtil } from '../src/FactoryQualifierUtil'; +import assert from 'assert'; describe('test/typing.test.ts', () => { it('should check enum value', async () => { @@ -23,4 +27,9 @@ describe('test/typing.test.ts', () => { .notExpect('code', 0) .end(); }); + + it('should dynamic range run', async () => { + const dynamics = FactoryQualifierUtil.getProperQualifiers(HelloService, 'eggObjectFactory'); + assert(dynamics.includes(AbstractContextHello)); + }); }); diff --git a/core/runtime/src/impl/EggObjectImpl.ts b/core/runtime/src/impl/EggObjectImpl.ts index ad197e27..b0458ea5 100644 --- a/core/runtime/src/impl/EggObjectImpl.ts +++ b/core/runtime/src/impl/EggObjectImpl.ts @@ -4,6 +4,7 @@ import type { EggObjectLifecycle, EggObjectLifeCycleContext, EggObjectName, + EggProtoImplClass, EggPrototype, ObjectInfo, QualifierInfo, } from '@eggjs/tegg-types'; import { EggObjectStatus, InjectType, ObjectInitType } from '@eggjs/tegg-types'; @@ -12,6 +13,7 @@ import { EggObjectLifecycleUtil } from '../model/EggObject'; import { EggContainerFactory } from '../factory/EggContainerFactory'; import { EggObjectUtil } from './EggObjectUtil'; import { ContextHandler } from '../model/ContextHandler'; +import { FactoryQualifierUtil } from '@eggjs/tegg-dynamic-inject'; export default class EggObjectImpl implements EggObject { private _obj: object; @@ -59,7 +61,30 @@ export default class EggObjectImpl implements EggObject { if (this.proto.initType !== ObjectInitType.CONTEXT && injectObject.proto.initType === ObjectInitType.CONTEXT) { this.injectProperty(injectObject.refName, EggObjectUtil.contextEggObjectGetProperty(proto, injectObject.objName)); } else { - const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName); + let injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName); + if (injectObj.proto.name === 'eggObjectFactory') { + const dynamics = FactoryQualifierUtil.getProperQualifiers(this._obj.constructor as unknown as EggProtoImplClass, injectObject.refName); + if (dynamics.length > 0) { + const obj: any = { + dynamics, + }; + const originalGetEggObject = (injectObj.obj as any).getEggObject; + Object.setPrototypeOf(obj, injectObj.obj); + obj.getEggObject = function(...args: any[]) { + if (!this.dynamics.includes(args[0])) { + throw new Error(`${args[0].name} is not in dynamic range: ${this.dynamics.map(item => item.name).join(', ')}`); + } + return originalGetEggObject.apply(this, args); + }; + injectObj = Object.create(injectObj); + Object.defineProperty(injectObj, 'obj', { + value: obj, + writable: true, + enumerable: true, + configurable: true, + }); + } + } this.injectProperty(injectObject.refName, EggObjectUtil.eggObjectGetProperty(injectObj)); } })); diff --git a/core/types/core-decorator/enum/Qualifier.ts b/core/types/core-decorator/enum/Qualifier.ts index 1732a800..6019a24c 100644 --- a/core/types/core-decorator/enum/Qualifier.ts +++ b/core/types/core-decorator/enum/Qualifier.ts @@ -10,4 +10,6 @@ export const QUALIFIER_META_DATA = Symbol.for('EggPrototype#qualifier'); export const PROPERTY_QUALIFIER_META_DATA = Symbol.for('EggPrototype#propertyQualifier'); +export const DYNAMIC_RANGE_META_DATA = Symbol.for('EggPrototype#FactoryQualifier'); + export const CONSTRUCTOR_QUALIFIER_META_DATA = Symbol.for('EggPrototype#constructorQualifier'); diff --git a/plugin/tegg/test/DynamicInject.test.ts b/plugin/tegg/test/DynamicInject.test.ts index b4585b5f..a0a5264b 100644 --- a/plugin/tegg/test/DynamicInject.test.ts +++ b/plugin/tegg/test/DynamicInject.test.ts @@ -48,4 +48,21 @@ describe('plugin/tegg/test/DynamicInject.test.ts', () => { 'hello, bar(singleton:1)', ]); }); + + it('dynamic range should work', async () => { + app.mockCsrf(); + const res = await app.httpRequest() + .get('/factoryQualifier') + .expect(200); + assert.deepStrictEqual(res.body, [ + 'AbstractContextHello is not in dynamic range: AbstractSingletonHello', + ]); + const res2 = await app.httpRequest() + .get('/singletonDynamicInject') + .expect(200); + assert.deepStrictEqual(res2.body, [ + 'hello, foo(singleton:2)', + 'hello, bar(singleton:2)', + ]); + }); }); diff --git a/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts b/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts index 430459fb..88ced236 100644 --- a/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts +++ b/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts @@ -16,4 +16,10 @@ export default class App extends Controller { this.ctx.status = 200; this.ctx.body = msgs; } + async factoryQualifier() { + const helloService: HelloService = await (this.ctx.module as any).dynamicInjectModule.factoryQualifierService; + const msgs = await helloService.hello(); + this.ctx.status = 200; + this.ctx.body = msgs; + } } diff --git a/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts b/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts index 22685efe..4522097c 100644 --- a/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts +++ b/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts @@ -3,4 +3,5 @@ import { Application } from 'egg'; module.exports = (app: Application) => { app.router.get('/dynamicInject', app.controller.app.dynamicInject); app.router.get('/singletonDynamicInject', app.controller.app.singletonDynamicInject); + app.router.get('/factoryQualifier', app.controller.app.factoryQualifier); }; diff --git a/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FactoryQualifierService.ts b/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FactoryQualifierService.ts new file mode 100644 index 00000000..3dd28f41 --- /dev/null +++ b/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FactoryQualifierService.ts @@ -0,0 +1,23 @@ +import { AccessLevel, Inject, EggObjectFactory, ContextProto, FactoryQualifier } from '@eggjs/tegg'; +import { ContextHelloType } from './FooType'; +import { AbstractSingletonHello } from './AbstractSingletonHello'; +import { AbstractContextHello } from './AbstractContextHello'; + +@ContextProto({ + accessLevel: AccessLevel.PUBLIC, +}) +export class FactoryQualifierService { + @Inject() + @FactoryQualifier(AbstractSingletonHello) + private readonly eggObjectFactory: EggObjectFactory; + + async hello(): Promise { + try { + const helloImpl = await this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.FOO); + const msgs = [ helloImpl.hello() ]; + return msgs; + } catch (err) { + return [ err.message ]; + } + } +} From f1cf27deb6369d886394285951e1e369509883ed Mon Sep 17 00:00:00 2001 From: akitaSummer Date: Sun, 21 Dec 2025 02:21:04 +0800 Subject: [PATCH 2/2] fix: ci --- core/dynamic-inject/test/typing.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dynamic-inject/test/typing.test.ts b/core/dynamic-inject/test/typing.test.ts index 26cdce61..d3cd9cc8 100644 --- a/core/dynamic-inject/test/typing.test.ts +++ b/core/dynamic-inject/test/typing.test.ts @@ -1,6 +1,6 @@ import path from 'path'; import coffee from 'coffee'; -import { HelloService } from './fixtures/modules/FactoryQualifier/HelloService'; +import { HelloService } from './fixtures/modules/dynamicRange/HelloService'; import { AbstractContextHello } from './fixtures/modules/base/AbstractContextHello'; import { FactoryQualifierUtil } from '../src/FactoryQualifierUtil'; import assert from 'assert';