diff --git a/core/core-decorator/index.ts b/core/core-decorator/index.ts index 6ea6eee7..0e2b9fe6 100644 --- a/core/core-decorator/index.ts +++ b/core/core-decorator/index.ts @@ -7,6 +7,7 @@ export * from './src/decorator/ContextProto'; export * from './src/decorator/SingletonProto'; export * from './src/decorator/EggQualifier'; export * from './src/decorator/MultiInstanceProto'; +export * from './src/decorator/MultiInstanceInfo'; export * from './src/decorator/ConfigSource'; export * from './src/util/MetadataUtil'; diff --git a/core/core-decorator/src/decorator/MultiInstanceInfo.ts b/core/core-decorator/src/decorator/MultiInstanceInfo.ts new file mode 100644 index 00000000..bfc0e70b --- /dev/null +++ b/core/core-decorator/src/decorator/MultiInstanceInfo.ts @@ -0,0 +1,9 @@ +import { PrototypeUtil } from '../util/PrototypeUtil'; +import { QualifierAttribute } from '@eggjs/tegg-types'; + +export function MultiInstanceInfo(attributes: QualifierAttribute[]) { + return function(target: any, _propertyKey: PropertyKey | undefined, parameterIndex: number) { + PrototypeUtil.setMultiInstanceConstructorIndex(target, parameterIndex); + PrototypeUtil.setMultiInstanceConstructorAttributes(target, attributes); + }; +} diff --git a/core/core-decorator/src/util/PrototypeUtil.ts b/core/core-decorator/src/util/PrototypeUtil.ts index 27f4921a..fbba59df 100644 --- a/core/core-decorator/src/util/PrototypeUtil.ts +++ b/core/core-decorator/src/util/PrototypeUtil.ts @@ -7,7 +7,7 @@ import { InjectConstructorInfo, InjectObjectInfo, InjectType, - MultiInstancePrototypeGetObjectsContext, + MultiInstancePrototypeGetObjectsContext, QualifierAttribute, } from '@eggjs/tegg-types'; import { MetadataUtil } from './MetadataUtil'; @@ -22,6 +22,8 @@ export class PrototypeUtil { static readonly INJECT_TYPE = Symbol.for('EggPrototype.injectType'); static readonly INJECT_CONSTRUCTOR_NAME_SET = Symbol.for('EggPrototype.injectConstructorNames'); static readonly CLAZZ_PROTO = Symbol.for('EggPrototype.clazzProto'); + static readonly MULTI_INSTANCE_CONSTRUCTOR_INDEX = Symbol.for('EggPrototype#multiInstanceConstructorIndex'); + static readonly MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES = Symbol.for('EggPrototype#multiInstanceConstructorAttributes'); /** * Mark class is egg object prototype @@ -146,6 +148,22 @@ export class PrototypeUtil { } } + static setMultiInstanceConstructorAttributes(clazz: EggProtoImplClass, attributes: QualifierAttribute[]) { + MetadataUtil.defineMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES, attributes, clazz); + } + + static getMultiInstanceConstructorAttributes(clazz: EggProtoImplClass): QualifierAttribute[] { + return MetadataUtil.getMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES, clazz) || []; + } + + static setMultiInstanceConstructorIndex(clazz: EggProtoImplClass, index: number) { + MetadataUtil.defineMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_INDEX, index, clazz); + } + + static getMultiInstanceConstructorIndex(clazz: EggProtoImplClass): number | undefined { + return MetadataUtil.getMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_INDEX, clazz); + } + static setInjectType(clazz: EggProtoImplClass, type: InjectType) { const injectType: InjectType | undefined = MetadataUtil.getMetaData(PrototypeUtil.INJECT_TYPE, clazz); if (!injectType) { diff --git a/core/metadata/src/impl/EggPrototypeBuilder.ts b/core/metadata/src/impl/EggPrototypeBuilder.ts index d5f8aaf1..cda7fadb 100644 --- a/core/metadata/src/impl/EggPrototypeBuilder.ts +++ b/core/metadata/src/impl/EggPrototypeBuilder.ts @@ -1,5 +1,5 @@ import assert from 'node:assert'; -import { InjectType, PrototypeUtil, QualifierUtil } from '@eggjs/core-decorator'; +import { InjectType, PrototypeUtil, QualifierAttribute, QualifierUtil } from '@eggjs/core-decorator'; import type { AccessLevel, EggProtoImplClass, @@ -35,6 +35,8 @@ export class EggPrototypeBuilder { private loadUnit: LoadUnit; private qualifiers: QualifierInfo[] = []; private className?: string; + private multiInstanceConstructorIndex?: number; + private multiInstanceConstructorAttributes?: QualifierAttribute[]; static create(ctx: EggPrototypeLifecycleContext): EggPrototype { const { clazz, loadUnit } = ctx; @@ -54,6 +56,8 @@ export class EggPrototypeBuilder { ...QualifierUtil.getProtoQualifiers(clazz), ...(ctx.prototypeInfo.qualifiers ?? []), ]; + builder.multiInstanceConstructorIndex = PrototypeUtil.getMultiInstanceConstructorIndex(clazz); + builder.multiInstanceConstructorAttributes = PrototypeUtil.getMultiInstanceConstructorAttributes(clazz); return builder.build(); } @@ -140,6 +144,8 @@ export class EggPrototypeBuilder { this.qualifiers, this.className, this.injectType, + this.multiInstanceConstructorIndex, + this.multiInstanceConstructorAttributes, ); } } diff --git a/core/metadata/src/impl/EggPrototypeImpl.ts b/core/metadata/src/impl/EggPrototypeImpl.ts index 47ad91f6..f8d28105 100644 --- a/core/metadata/src/impl/EggPrototypeImpl.ts +++ b/core/metadata/src/impl/EggPrototypeImpl.ts @@ -1,4 +1,4 @@ -import { InjectType, MetadataUtil } from '@eggjs/core-decorator'; +import { InjectType, MetadataUtil, QualifierAttribute } from '@eggjs/core-decorator'; import type { AccessLevel, EggProtoImplClass, @@ -26,6 +26,8 @@ export class EggPrototypeImpl implements EggPrototype { readonly injectType: InjectType; readonly loadUnitId: Id; readonly className?: string; + readonly multiInstanceConstructorIndex?: number; + readonly multiInstanceConstructorAttributes?: QualifierAttribute[]; constructor( id: string, @@ -39,6 +41,8 @@ export class EggPrototypeImpl implements EggPrototype { qualifiers: QualifierInfo[], className?: string, injectType?: InjectType, + multiInstanceConstructorIndex?: number, + multiInstanceConstructorAttributes?: QualifierAttribute[], ) { this.id = id; this.clazz = clazz; @@ -51,6 +55,8 @@ export class EggPrototypeImpl implements EggPrototype { this.qualifiers = qualifiers; this.className = className; this.injectType = injectType || InjectType.PROPERTY; + this.multiInstanceConstructorIndex = multiInstanceConstructorIndex; + this.multiInstanceConstructorAttributes = multiInstanceConstructorAttributes; } verifyQualifiers(qualifiers: QualifierInfo[]): boolean { diff --git a/core/runtime/src/impl/EggObjectImpl.ts b/core/runtime/src/impl/EggObjectImpl.ts index cc90e43d..ad197e27 100644 --- a/core/runtime/src/impl/EggObjectImpl.ts +++ b/core/runtime/src/impl/EggObjectImpl.ts @@ -4,7 +4,7 @@ import type { EggObjectLifecycle, EggObjectLifeCycleContext, EggObjectName, - EggPrototype, + EggPrototype, ObjectInfo, QualifierInfo, } from '@eggjs/tegg-types'; import { EggObjectStatus, InjectType, ObjectInitType } from '@eggjs/tegg-types'; import { IdenticalUtil } from '@eggjs/tegg-lifecycle'; @@ -92,7 +92,7 @@ export default class EggObjectImpl implements EggObject { // 4. call obj lifecycle postCreate // 5. success create try { - const constructArgs = await Promise.all(this.proto.injectObjects!.map(async injectObject => { + const constructArgs: any[] = await Promise.all(this.proto.injectObjects!.map(async injectObject => { const proto = injectObject.proto; const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId); if (!loadUnit) { @@ -104,6 +104,22 @@ export default class EggObjectImpl implements EggObject { const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName); return EggObjectUtil.eggObjectProxy(injectObj); })); + if (typeof this.proto.multiInstanceConstructorIndex !== 'undefined') { + const qualifiers = this.proto.multiInstanceConstructorAttributes + ?.map(t => { + return { + attribute: t, + value: this.proto.getQualifier(t), + } as QualifierInfo; + }) + ?.filter(t => typeof t.value !== 'undefined') + ?? []; + const objInfo: ObjectInfo = { + name: this.proto.name, + qualifiers, + }; + constructArgs.splice(this.proto.multiInstanceConstructorIndex, 0, objInfo); + } this._obj = this.proto.constructEggObject(...constructArgs); const objLifecycleHook = this._obj as EggObjectLifecycle; diff --git a/core/runtime/test/LoadUnitInstance.test.ts b/core/runtime/test/LoadUnitInstance.test.ts index e71fb7f5..ab633a8d 100644 --- a/core/runtime/test/LoadUnitInstance.test.ts +++ b/core/runtime/test/LoadUnitInstance.test.ts @@ -11,6 +11,7 @@ import { Bar, Foo } from './fixtures/modules/extends-module/Base'; import { ContextHandler } from '../src/model/ContextHandler'; import { EggContextStorage } from './fixtures/EggContextStorage'; import { FOO_ATTRIBUTE, FooLogger } from './fixtures/modules/multi-instance-module/MultiInstance'; +import { FooLoggerConstructor } from './fixtures/modules/multi-instance-module/MultiInstanceConstructor'; describe('test/LoadUnit/LoadUnitInstance.test.ts', () => { describe('ModuleLoadUnitInstance', () => { @@ -101,6 +102,43 @@ describe('test/LoadUnit/LoadUnitInstance.test.ts', () => { await TestUtil.destroyLoadUnitInstance(instance); }); + + it('should load multi instance with constructor', async () => { + const instance = await TestUtil.createLoadUnitInstance('multi-instance-module'); + const foo1Proto = EggPrototypeFactory.instance.getPrototype('fooConstructor', instance.loadUnit, [{ + attribute: FOO_ATTRIBUTE, + value: 'foo1', + }]); + const foo1Obj = await EggContainerFactory.getOrCreateEggObject(foo1Proto, foo1Proto.name); + const foo1 = foo1Obj.obj as FooLoggerConstructor; + + const foo2Proto = EggPrototypeFactory.instance.getPrototype('fooConstructor', instance.loadUnit, [{ + attribute: FOO_ATTRIBUTE, + value: 'foo2', + }]); + const foo2Obj = await EggContainerFactory.getOrCreateEggObject(foo2Proto, foo2Proto.name); + const foo2 = foo2Obj.obj as FooLoggerConstructor; + assert(foo1); + assert(foo2); + assert(foo1 !== foo2); + assert(foo1.foo === 'foo1'); + assert(foo2.foo === 'foo2'); + assert(foo1.bar === 'bar'); + assert(foo2.foo === 'foo2'); + + const obj1 = await EggContainerFactory.getOrCreateEggObjectFromClazz(FooLogger, 'fooConstructor', [{ + attribute: FOO_ATTRIBUTE, + value: 'foo1', + }]); + const obj2 = await EggContainerFactory.getOrCreateEggObjectFromClazz(FooLogger, 'fooConstructor', [{ + attribute: FOO_ATTRIBUTE, + value: 'foo2', + }]); + assert(foo1Obj === obj1); + assert(foo2Obj === obj2); + + await TestUtil.destroyLoadUnitInstance(instance); + }); }); describe('MultiModule', () => { diff --git a/core/runtime/test/fixtures/modules/multi-instance-module/MultiInstanceConstructor.ts b/core/runtime/test/fixtures/modules/multi-instance-module/MultiInstanceConstructor.ts new file mode 100644 index 00000000..b2a0ca28 --- /dev/null +++ b/core/runtime/test/fixtures/modules/multi-instance-module/MultiInstanceConstructor.ts @@ -0,0 +1,41 @@ +import { + AccessLevel, + ObjectInitType, + QualifierValue, + ObjectInfo +} from '@eggjs/tegg-types'; +import { Inject, MultiInstanceInfo, MultiInstanceProto, SingletonProto } from '@eggjs/core-decorator'; + +export const FOO_ATTRIBUTE = Symbol.for('FOO_ATTRIBUTE'); + +@SingletonProto() +export class Bar { + bar = 'bar'; +} + +@MultiInstanceProto({ + accessLevel: AccessLevel.PUBLIC, + initType: ObjectInitType.SINGLETON, + objects: [{ + name: 'fooConstructor', + qualifiers: [{ + attribute: FOO_ATTRIBUTE, + value: 'foo1', + }], + }, { + name: 'fooConstructor', + qualifiers: [{ + attribute: FOO_ATTRIBUTE, + value: 'foo2', + }], + }], +}) +export class FooLoggerConstructor { + foo: QualifierValue | undefined; + bar: string; + + constructor(@Inject() bar: Bar, @MultiInstanceInfo([ FOO_ATTRIBUTE ]) objInfo: ObjectInfo) { + this.foo = objInfo.qualifiers.find(t => t.attribute === FOO_ATTRIBUTE)?.value; + this.bar = bar.bar; + } +} diff --git a/core/types/metadata/model/EggPrototype.ts b/core/types/metadata/model/EggPrototype.ts index 3567325e..a3ad62d7 100644 --- a/core/types/metadata/model/EggPrototype.ts +++ b/core/types/metadata/model/EggPrototype.ts @@ -109,6 +109,8 @@ export interface EggPrototype extends LifecycleObject; readonly injectType?: InjectType; readonly className?: string; + readonly multiInstanceConstructorIndex?: number; + readonly multiInstanceConstructorAttributes?: QualifierAttribute[]; /** * get metedata for key diff --git a/plugin/tegg/typings/index.d.ts b/plugin/tegg/typings/index.d.ts index 6fac779e..df1acc97 100644 --- a/plugin/tegg/typings/index.d.ts +++ b/plugin/tegg/typings/index.d.ts @@ -59,7 +59,7 @@ declare module 'egg' { // 兼容现有 module 的定义 module: EggModule & EggApplicationModule; - getEggObject(clazz: EggProtoImplClass): Promise; + getEggObject(clazz: EggProtoImplClass, name?: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise; getEggObjectFromName(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise; } @@ -68,7 +68,7 @@ declare module 'egg' { // 兼容现有 module 的定义 module: EggModule & EggContextModule; - getEggObject(clazz: EggProtoImplClass): Promise; + getEggObject(clazz: EggProtoImplClass, name?: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise; } interface Application extends TEggApplication {