diff --git a/core/core-decorator/src/util/PrototypeUtil.ts b/core/core-decorator/src/util/PrototypeUtil.ts index a6c62ade..c6d02385 100644 --- a/core/core-decorator/src/util/PrototypeUtil.ts +++ b/core/core-decorator/src/util/PrototypeUtil.ts @@ -132,15 +132,11 @@ export class PrototypeUtil { return metadata; } const callBackMetadata = MetadataUtil.getMetaData(this.MULTI_INSTANCE_PROTOTYPE_CALLBACK_PROPERTY, clazz); - // callback only be called once - // cache result to static property if (callBackMetadata) { - const metadata = { + return { ...callBackMetadata, objects: callBackMetadata.getObjects(ctx), }; - this.setMultiInstanceStaticProperty(clazz, metadata); - return metadata; } } diff --git a/core/metadata/index.ts b/core/metadata/index.ts index f7adfd33..da61c6b0 100644 --- a/core/metadata/index.ts +++ b/core/metadata/index.ts @@ -7,6 +7,8 @@ export * from './src/model/LoadUnit'; export * from './src/model/Loader'; export * from './src/errors'; export * from './src/util/ClassUtil'; +export * from './src/impl/LoadUnitMultiInstanceProtoHook'; +export * from './src/model/AppGraph'; import './src/impl/ModuleLoadUnit'; import './src/impl/EggPrototypeBuilder'; diff --git a/core/metadata/src/impl/LoadUnitMultiInstanceProtoHook.ts b/core/metadata/src/impl/LoadUnitMultiInstanceProtoHook.ts new file mode 100644 index 00000000..ba3c7a3a --- /dev/null +++ b/core/metadata/src/impl/LoadUnitMultiInstanceProtoHook.ts @@ -0,0 +1,28 @@ +import { LifecycleHook } from '@eggjs/tegg-lifecycle'; +import { EggProtoImplClass, PrototypeUtil } from '@eggjs/core-decorator'; +import { + EggPrototypeCreatorFactory, EggPrototypeFactory, + LoadUnit, + LoadUnitLifecycleContext, +} from '@eggjs/tegg-metadata'; + +export class LoadUnitMultiInstanceProtoHook implements LifecycleHook { + multiInstanceClazzSet: Set = new Set(); + + async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise { + const clazzList = ctx.loader.load(); + const multiInstanceClazzList = Array.from(this.multiInstanceClazzSet); + for (const clazz of clazzList) { + if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) { + this.multiInstanceClazzSet.add(clazz); + } + } + for (const clazz of multiInstanceClazzList) { + const protos = await EggPrototypeCreatorFactory.createProto(clazz, loadUnit); + for (const proto of protos) { + EggPrototypeFactory.instance.registerPrototype(proto, loadUnit); + } + } + } +} + diff --git a/core/metadata/src/model/AppGraph.ts b/core/metadata/src/model/AppGraph.ts new file mode 100644 index 00000000..461a5516 --- /dev/null +++ b/core/metadata/src/model/AppGraph.ts @@ -0,0 +1,254 @@ +import assert from 'node:assert'; +import util from 'node:util'; +import { Graph, GraphNode, GraphNodeObj, ModuleConfigUtil, ModuleReference } from '@eggjs/tegg-common-util'; +import { + AccessLevel, + EggProtoImplClass, + EggPrototypeName, + INIT_TYPE_TRY_ORDER, + InitTypeQualifierAttribute, + LoadUnitNameQualifierAttribute, + PrototypeUtil, + QualifierInfo, + QualifierUtil, +} from '@eggjs/core-decorator'; + +export interface InstanceClazzMeta { + name: PropertyKey; + qualifiers: QualifierInfo[]; + accessLevel: AccessLevel, + instanceModule: GraphNode; + ownerModule: GraphNode; +} + +export type ClazzMetaMap = Record; + +function verifyQualifier(clazzQualifiers: QualifierInfo[], qualifier: QualifierInfo) { + const selfQualifiers = clazzQualifiers.find(t => t.attribute === qualifier.attribute); + return selfQualifiers?.value === qualifier.value; +} + +function verifyQualifiers(clazzQualifiers: QualifierInfo[], qualifiers: QualifierInfo[]) { + for (const qualifier of qualifiers) { + if (!verifyQualifier(clazzQualifiers, qualifier)) { + return false; + } + } + return true; +} + +export class ClazzMap { + private clazzMap: ClazzMetaMap; + + constructor(graph: Graph) { + this.build(graph); + } + + private build(graph: Graph) { + /** + * 1. iterate all module get all MultiInstanceClazz + * 2. iterate MultiInstanceClazz and all module get object meta + * 3. iterate object meta and build clazz map + */ + const clazzMap: ClazzMetaMap = {}; + for (const ownerNode of graph.nodes.values()) { + for (const clazz of ownerNode.val.getClazzList()) { + const qualifiers = QualifierUtil.getProtoQualifiers(clazz); + if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) { + for (const instanceNode of graph.nodes.values()) { + const property = PrototypeUtil.getMultiInstanceProperty(clazz, { + unitPath: instanceNode.val.moduleConfig.path, + }); + assert(property, `multi instance property not found for ${clazz.name}`); + for (const info of property.objects) { + clazzMap[info.name] = clazzMap[info.name] || []; + clazzMap[info.name].push({ + name: info.name, + accessLevel: PrototypeUtil.getAccessLevel(clazz, { + unitPath: instanceNode.val.moduleConfig.path, + }) as AccessLevel, + qualifiers: [ + ...qualifiers, + ...info.qualifiers, + ], + instanceModule: instanceNode, + ownerModule: ownerNode, + }); + } + } + } else { + const property = PrototypeUtil.getProperty(clazz); + assert(property, `property not found for ${clazz.name}`); + clazzMap[property.name] = clazzMap[property.name] || []; + clazzMap[property.name].push({ + name: property.name, + accessLevel: PrototypeUtil.getAccessLevel(clazz, { + unitPath: ownerNode.val.moduleConfig.path, + }) as AccessLevel, + qualifiers, + ownerModule: ownerNode, + instanceModule: ownerNode, + }); + } + } + } + this.clazzMap = clazzMap; + } + + findDependencyModule(objName: EggPrototypeName, properQualifiers: QualifierInfo[], intoModule: GraphNode): GraphNode[] { + const result: Set> = new Set(); + const objInfo = this.clazzMap[objName]; + if (!objInfo) { + return []; + } + let mayObjs = objInfo.filter(obj => { + // 1. check accessLevel + if (obj.instanceModule !== intoModule && obj.accessLevel === AccessLevel.PRIVATE) { + return false; + } + // 2. check qualifier + return verifyQualifiers(obj.qualifiers, properQualifiers); + }); + + // 3. auto set init type qualifier + if (mayObjs.length > 1) { + const initTypeQualifiers = INIT_TYPE_TRY_ORDER.map(type => ({ + attribute: InitTypeQualifierAttribute, + value: type, + })); + for (const initTypeQualifier of initTypeQualifiers) { + const mayInitTypeObjs = mayObjs.filter(obj => { + return verifyQualifiers(obj.qualifiers, [ + ...properQualifiers, + initTypeQualifier, + ]); + }); + if (mayInitTypeObjs.length > 0) { + mayObjs = mayInitTypeObjs; + } + } + } + + // 4. auto set load unit name qualifier + if (mayObjs.length > 1) { + const moduleNameQualifiers = { + attribute: LoadUnitNameQualifierAttribute, + value: intoModule.val.name, + }; + const mayLoadUnitNameObjs = mayObjs.filter(obj => { + return verifyQualifiers(obj.qualifiers, [ + ...properQualifiers, + moduleNameQualifiers, + ]); + }); + if (mayLoadUnitNameObjs.length > 0) { + mayObjs = mayLoadUnitNameObjs; + } + } + + if (mayObjs.length > 1) { + const message = util.format('multi class found for %s@%o in module %j', + objName, + properQualifiers, + mayObjs.map(t => { + return t.instanceModule.val.moduleConfig.path; + })); + throw new Error(message); + } + + for (const obj of mayObjs) { + result.add(obj.instanceModule); + result.add(obj.ownerModule); + } + return Array.from(result); + } +} + +export class ModuleNode implements GraphNodeObj { + readonly id: string; + readonly name: string; + readonly moduleConfig: ModuleReference; + private readonly clazzList: EggProtoImplClass[]; + + constructor(moduleConfig: ModuleReference) { + this.moduleConfig = moduleConfig; + this.id = moduleConfig.path; + this.name = ModuleConfigUtil.readModuleNameSync(moduleConfig.path); + this.clazzList = []; + } + + addClazz(clazz: EggProtoImplClass) { + if (!this.clazzList.includes(clazz)) { + this.clazzList.push(clazz); + } + const defaultQualifier = [{ + attribute: InitTypeQualifierAttribute, + value: PrototypeUtil.getInitType(clazz, { + unitPath: this.moduleConfig.path, + })!, + }, { + attribute: LoadUnitNameQualifierAttribute, + value: this.name, + }]; + for (const qualifier of defaultQualifier) { + QualifierUtil.addProtoQualifier(clazz, qualifier.attribute, qualifier.value); + } + } + + toString() { + return `${this.name}@${this.moduleConfig.path}`; + } + + getClazzList(): readonly EggProtoImplClass[] { + return this.clazzList; + } +} + +export class AppGraph { + private graph: Graph; + private clazzMap: ClazzMap; + moduleConfigList: Array; + + constructor() { + this.graph = new Graph(); + } + + addNode(moduleNode: ModuleNode) { + if (!this.graph.addVertex(new GraphNode(moduleNode))) { + throw new Error(`duplicate module: ${moduleNode}`); + } + } + + build() { + this.clazzMap = new ClazzMap(this.graph); + + // 1. iterate all modules + for (const node of this.graph.nodes.values()) { + // 2. iterate all class + for (const clazz of node.val.getClazzList()) { + const injectObjects = PrototypeUtil.getInjectObjects(clazz); + // 3. iterate all inject objects + for (const injectObject of injectObjects) { + const properQualifiers = QualifierUtil.getProperQualifiers(clazz, injectObject.refName); + // 4. find dependency module + const dependencyModules = this.clazzMap.findDependencyModule(injectObject.objName, properQualifiers, node); + for (const moduleNode of dependencyModules) { + // 5. add edge + if (node !== moduleNode) { + this.graph.addEdge(node, moduleNode); + } + } + } + } + } + } + + sort() { + const loopPath = this.graph.loopPath(); + if (loopPath) { + throw new Error('module has recursive deps: ' + loopPath); + } + this.moduleConfigList = this.graph.sort() + .map(t => t.val.moduleConfig); + } +} diff --git a/plugin/tegg/lib/EggModuleLoader.ts b/plugin/tegg/lib/EggModuleLoader.ts index e0eaefe9..afee5f5c 100644 --- a/plugin/tegg/lib/EggModuleLoader.ts +++ b/plugin/tegg/lib/EggModuleLoader.ts @@ -1,15 +1,7 @@ -import { EggLoadUnitType, Loader, LoadUnitFactory } from '@eggjs/tegg-metadata'; +import { EggLoadUnitType, Loader, LoadUnitFactory, AppGraph, ModuleNode } from '@eggjs/tegg-metadata'; import { LoaderFactory } from '@eggjs/tegg-loader'; import { EggAppLoader } from './EggAppLoader'; import { Application } from 'egg'; -import { AppGraph, ModuleNode } from './AppGraph'; -import { - InitTypeQualifierAttribute, - LoadUnitNameQualifierAttribute, - PrototypeUtil, - QualifierUtil, -} from '@eggjs/tegg'; -import { ModuleConfigUtil } from '@eggjs/tegg-common-util'; export class EggModuleLoader { app: Application; @@ -33,20 +25,6 @@ export class EggModuleLoader { loaderCache.set(modulePath, loader); const clazzList = loader.load(); for (const clazz of clazzList) { - // TODO copy from ModuleLoadUnit, duplicate code - const moduleName = ModuleConfigUtil.readModuleNameSync(modulePath); - const defaultQualifier = [{ - attribute: InitTypeQualifierAttribute, - value: PrototypeUtil.getInitType(clazz, { - unitPath: modulePath, - })!, - }, { - attribute: LoadUnitNameQualifierAttribute, - value: moduleName, - }]; - defaultQualifier.forEach(qualifier => { - QualifierUtil.addProtoQualifier(clazz, qualifier.attribute, qualifier.value); - }); moduleNode.addClazz(clazz); } appGraph.addNode(moduleNode); diff --git a/standalone/standalone/src/EggModuleLoader.ts b/standalone/standalone/src/EggModuleLoader.ts index 5534c3f9..c35b8b88 100644 --- a/standalone/standalone/src/EggModuleLoader.ts +++ b/standalone/standalone/src/EggModuleLoader.ts @@ -1,13 +1,7 @@ import { EggLoadUnitType, Loader, LoadUnit, LoadUnitFactory } from '@eggjs/tegg-metadata'; import { LoaderFactory } from '@eggjs/tegg-loader'; -import { StandaloneGraph, ModuleNode } from './StandaloneGraph'; -import { - InitTypeQualifierAttribute, - LoadUnitNameQualifierAttribute, - PrototypeUtil, - QualifierUtil, -} from '@eggjs/tegg'; -import { ModuleConfigUtil, ModuleReference } from '@eggjs/tegg-common-util'; +import { AppGraph, ModuleNode } from '@eggjs/tegg/helper'; +import { ModuleReference } from '@eggjs/tegg-common-util'; export class EggModuleLoader { private moduleReferences: readonly ModuleReference[]; @@ -17,7 +11,7 @@ export class EggModuleLoader { } private buildAppGraph(loaderCache: Map) { - const appGraph = new StandaloneGraph(); + const appGraph = new AppGraph(); for (const moduleConfig of this.moduleReferences) { const modulePath = moduleConfig.path; const moduleNode = new ModuleNode(moduleConfig); @@ -25,20 +19,6 @@ export class EggModuleLoader { loaderCache.set(modulePath, loader); const clazzList = loader.load(); for (const clazz of clazzList) { - // TODO copy from ModuleLoadUnit, duplicate code - const moduleName = ModuleConfigUtil.readModuleNameSync(modulePath); - const defaultQualifier = [{ - attribute: InitTypeQualifierAttribute, - value: PrototypeUtil.getInitType(clazz, { - unitPath: modulePath, - })!, - }, { - attribute: LoadUnitNameQualifierAttribute, - value: moduleName, - }]; - defaultQualifier.forEach(qualifier => { - QualifierUtil.addProtoQualifier(clazz, qualifier.attribute, qualifier.value); - }); moduleNode.addClazz(clazz); } appGraph.addNode(moduleNode); diff --git a/standalone/standalone/src/Runner.ts b/standalone/standalone/src/Runner.ts index 52753732..866d2693 100644 --- a/standalone/standalone/src/Runner.ts +++ b/standalone/standalone/src/Runner.ts @@ -3,7 +3,7 @@ import { EggPrototype, EggPrototypeLifecycleUtil, LoadUnit, LoadUnitFactory, - LoadUnitLifecycleUtil, + LoadUnitLifecycleUtil, LoadUnitMultiInstanceProtoHook, } from '@eggjs/tegg-metadata'; import { ContextHandler, @@ -43,6 +43,7 @@ export class Runner { private loadUnitLoader: EggModuleLoader; private runnerProto: EggPrototype; private configSourceEggPrototypeHook: ConfigSourceLoadUnitHook; + private loadUnitMultiInstanceProtoHook: LoadUnitMultiInstanceProtoHook; private readonly loadUnitInnerClassHook: LoadUnitInnerClassHook; private readonly crosscutAdviceFactory: CrosscutAdviceFactory; @@ -120,6 +121,9 @@ export class Runner { EggPrototypeLifecycleUtil.registerLifecycle(this.eggPrototypeCrossCutHook); LoadUnitLifecycleUtil.registerLifecycle(this.loadUnitAopHook); EggObjectLifecycleUtil.registerLifecycle(this.eggObjectAopHook); + + this.loadUnitMultiInstanceProtoHook = new LoadUnitMultiInstanceProtoHook(); + LoadUnitLifecycleUtil.registerLifecycle(this.loadUnitMultiInstanceProtoHook); } async init() { @@ -203,5 +207,9 @@ export class Runner { if (this.eggObjectAopHook) { EggObjectLifecycleUtil.deleteLifecycle(this.eggObjectAopHook); } + + if (this.loadUnitMultiInstanceProtoHook) { + LoadUnitLifecycleUtil.deleteLifecycle(this.loadUnitMultiInstanceProtoHook); + } } } diff --git a/standalone/standalone/src/StandaloneGraph.ts b/standalone/standalone/src/StandaloneGraph.ts deleted file mode 100644 index ad0f933e..00000000 --- a/standalone/standalone/src/StandaloneGraph.ts +++ /dev/null @@ -1,153 +0,0 @@ -import assert from 'assert'; -import path from 'path'; -import { Graph, GraphNode, GraphNodeObj } from '@eggjs/tegg-common-util'; -import { - EggProtoImplClass, - EggPrototypeName, - INIT_TYPE_TRY_ORDER, - InitTypeQualifierAttribute, - LoadUnitNameQualifierAttribute, - PrototypeUtil, - QualifierInfo, - QualifierUtil, - AccessLevel, -} from '@eggjs/tegg'; - -export interface ModuleConfig { - path: string; -} - -export class ModuleNode implements GraphNodeObj { - readonly id: string; - readonly name: string; - readonly moduleConfig: ModuleConfig; - private clazzList: EggProtoImplClass[]; - - // TODO refactor to ModuleUtil - static readModuleName(modulePath: string): string { - const pkgPath = path.join(modulePath, 'package.json'); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const pkg = require(pkgPath); - assert(pkg.eggModule, `module config not found in package ${pkgPath}`); - const { name } = pkg.eggModule; - return name; - } - - constructor(moduleConfig: ModuleConfig) { - this.moduleConfig = moduleConfig; - this.id = moduleConfig.path; - this.name = ModuleNode.readModuleName(moduleConfig.path); - this.clazzList = []; - } - - addClazz(clazz: EggProtoImplClass) { - this.clazzList.push(clazz); - } - - getClazzList(): readonly EggProtoImplClass[] { - return this.clazzList; - } - - getPublicClazzList(): readonly EggProtoImplClass[] { - return this.clazzList.filter(clazz => PrototypeUtil.getInitType(clazz, { - unitPath: this.moduleConfig.path, - }) === AccessLevel.PUBLIC); - } - - toString() { - return `${this.name}@${this.moduleConfig.path}`; - } - - verifyQualifiers(clazz: EggProtoImplClass, qualifiers: QualifierInfo[]): boolean { - const clazzQualifiers = QualifierUtil.getProtoQualifiers(clazz); - for (const qualifier of qualifiers) { - if (!this.verifyQualifier(clazzQualifiers, qualifier)) { - return false; - } - } - return true; - } - - verifyQualifier(clazzQualifiers: QualifierInfo[], qualifier: QualifierInfo) { - const selfQualifiers = clazzQualifiers.find(t => t.attribute === qualifier.attribute); - return selfQualifiers?.value === qualifier.value; - } - - findImplementClazzList(objName: EggPrototypeName, qualifiers: QualifierInfo[], innerFind: boolean): EggProtoImplClass[] { - const moduleQualifier = qualifiers.find(t => t.attribute === LoadUnitNameQualifierAttribute); - if (moduleQualifier && moduleQualifier.value !== this.name) { - return []; - } - const clazzList = innerFind ? this.clazzList : this.getPublicClazzList(); - const implList = clazzList - .filter(clazz => PrototypeUtil.getObjNames(clazz, { - unitPath: this.moduleConfig.path, - }).includes(objName)) - .filter(clazz => this.verifyQualifiers(clazz, qualifiers)); - if (implList.length === 1) { - return implList; - } - const initTypeQualifiers = INIT_TYPE_TRY_ORDER.map(type => ({ - attribute: InitTypeQualifierAttribute, - value: type, - })); - for (const initTypeQualifier of initTypeQualifiers) { - const initTypeList = implList.filter(clazz => this.verifyQualifiers(clazz, [ initTypeQualifier ])); - if (initTypeList.length === 1) { - return initTypeList; - } - } - return implList; - } -} - -export class StandaloneGraph { - private graph: Graph; - moduleConfigList: Array; - - constructor() { - this.graph = new Graph(); - } - - addNode(moduleNode: ModuleNode) { - if (!this.graph.addVertex(new GraphNode(moduleNode))) { - throw new Error(`duplicate module: ${moduleNode}`); - } - } - - private findDependencyModule(objName: EggPrototypeName, properqualifiers: QualifierInfo[], protoQualifiers: QualifierInfo[]): Array> { - const nodes: Array> = Array.from(this.graph.nodes.values()); - const hostModuleName = protoQualifiers.find(t => t.attribute === LoadUnitNameQualifierAttribute)?.value; - return nodes.filter(node => { - // private 的类只能在当前 module 中被找到 - const clazzList = node.val.findImplementClazzList(objName, properqualifiers, hostModuleName === node.val.name); - return clazzList.length; - }); - } - - build() { - for (const node of this.graph.nodes.values()) { - for (const clazz of node.val.getClazzList()) { - const injectObjects = PrototypeUtil.getInjectObjects(clazz); - for (const injectObject of injectObjects) { - const properqualifiers = QualifierUtil.getProperQualifiers(clazz, injectObject.refName); - const protoQualifiers = QualifierUtil.getProtoQualifiers(clazz); - const dependencyModules = this.findDependencyModule(injectObject.objName, properqualifiers, protoQualifiers); - for (const moduleNode of dependencyModules) { - if (node !== moduleNode) { - this.graph.addEdge(node, moduleNode); - } - } - } - } - } - } - - sort() { - const loopPath = this.graph.loopPath(); - if (loopPath) { - throw new Error('module has recursive deps: ' + loopPath); - } - this.moduleConfigList = this.graph.sort().map(t => t.val.moduleConfig); - } -} diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/biz.ts b/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/biz.ts new file mode 100644 index 00000000..4426e917 --- /dev/null +++ b/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/biz.ts @@ -0,0 +1,24 @@ +import { Inject, SingletonProto, AccessLevel } from '@eggjs/tegg'; +import { DynamicLogger, LogPath } from '../logger/DynamicLogger'; + +@SingletonProto({ + accessLevel: AccessLevel.PUBLIC, +}) +export class Biz { + @Inject({ + name: 'dynamicLogger', + }) + @LogPath('fooBiz') + fooDynamicLogger: DynamicLogger; + + @Inject({ + name: 'dynamicLogger', + }) + @LogPath('barBiz') + barDynamicLogger: DynamicLogger; + + async doSomething(): Promise { + await this.fooDynamicLogger.info('hello, foo biz'); + await this.barDynamicLogger.info('hello, bar biz'); + } +} diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/module.yml b/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/module.yml new file mode 100644 index 00000000..bd67fdd0 --- /dev/null +++ b/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/module.yml @@ -0,0 +1,4 @@ +features: + logger: + - fooBiz + - barBiz diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/package.json b/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/package.json new file mode 100644 index 00000000..f162d826 --- /dev/null +++ b/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/package.json @@ -0,0 +1,6 @@ +{ + "name": "multi-callback-instance-module-biz", + "eggModule": { + "name": "multiCallbackInstanceModuleBiz" + } +} diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/DynamicLogger.ts b/standalone/standalone/test/fixtures/multi-callback-instance-module/logger/DynamicLogger.ts similarity index 91% rename from standalone/standalone/test/fixtures/multi-callback-instance-module/DynamicLogger.ts rename to standalone/standalone/test/fixtures/multi-callback-instance-module/logger/DynamicLogger.ts index 62c94087..fb19e007 100644 --- a/standalone/standalone/test/fixtures/multi-callback-instance-module/DynamicLogger.ts +++ b/standalone/standalone/test/fixtures/multi-callback-instance-module/logger/DynamicLogger.ts @@ -5,6 +5,7 @@ import { LifecycleDestroy, QualifierUtil, EggProtoImplClass, + AccessLevel, } from '@eggjs/tegg'; import { EggObject, ModuleConfigUtil, EggObjectLifeCycleContext } from '@eggjs/tegg/helper'; import fs from 'node:fs'; @@ -22,9 +23,14 @@ export function LogPath(name: string) { @MultiInstanceProto({ + accessLevel: AccessLevel.PUBLIC, getObjects(ctx: MultiInstancePrototypeGetObjectsContext) { const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath); - return (config as any).features.logger.map(name => { + const logger = (config as any)?.features.logger; + if (!logger) { + return []; + } + return logger.map(name => { return { name: 'dynamicLogger', qualifiers: [{ diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/logger/package.json b/standalone/standalone/test/fixtures/multi-callback-instance-module/logger/package.json new file mode 100644 index 00000000..aba4cfff --- /dev/null +++ b/standalone/standalone/test/fixtures/multi-callback-instance-module/logger/package.json @@ -0,0 +1,6 @@ +{ + "name": "multi-callback-instance-module-logger", + "eggModule": { + "name": "multiCallbackInstanceModuleLogger" + } +} diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/foo.ts b/standalone/standalone/test/fixtures/multi-callback-instance-module/main/foo.ts similarity index 76% rename from standalone/standalone/test/fixtures/multi-callback-instance-module/foo.ts rename to standalone/standalone/test/fixtures/multi-callback-instance-module/main/foo.ts index 1eca7f3d..d0a2203b 100644 --- a/standalone/standalone/test/fixtures/multi-callback-instance-module/foo.ts +++ b/standalone/standalone/test/fixtures/multi-callback-instance-module/main/foo.ts @@ -1,6 +1,7 @@ import { Inject, SingletonProto } from '@eggjs/tegg'; import { Runner, MainRunner } from '@eggjs/tegg/standalone'; -import { DynamicLogger, LogPath } from './DynamicLogger'; +import { DynamicLogger, LogPath } from '../logger/DynamicLogger'; +import { Biz } from '../biz/biz'; @SingletonProto() @Runner() @@ -17,8 +18,12 @@ export class Foo implements MainRunner { @LogPath('bar') barDynamicLogger: DynamicLogger; + @Inject() + biz: Biz; + async main(): Promise { await this.fooDynamicLogger.info('hello, foo'); await this.barDynamicLogger.info('hello, bar'); + await this.biz.doSomething(); } } diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/module.yml b/standalone/standalone/test/fixtures/multi-callback-instance-module/main/module.yml similarity index 100% rename from standalone/standalone/test/fixtures/multi-callback-instance-module/module.yml rename to standalone/standalone/test/fixtures/multi-callback-instance-module/main/module.yml diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/main/package.json b/standalone/standalone/test/fixtures/multi-callback-instance-module/main/package.json new file mode 100644 index 00000000..d7184f72 --- /dev/null +++ b/standalone/standalone/test/fixtures/multi-callback-instance-module/main/package.json @@ -0,0 +1,6 @@ +{ + "name": "multi-callback-instance-module-main", + "eggModule": { + "name": "multiCallbackInstanceModuleMain" + } +} diff --git a/standalone/standalone/test/fixtures/multi-callback-instance-module/package.json b/standalone/standalone/test/fixtures/multi-callback-instance-module/package.json deleted file mode 100644 index 3120ad3f..00000000 --- a/standalone/standalone/test/fixtures/multi-callback-instance-module/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "multi-callback-instance-module", - "eggModule": { - "name": "multiCallbackInstanceModule" - } -} diff --git a/standalone/standalone/test/index.test.ts b/standalone/standalone/test/index.test.ts index 78a8f877..0e7d9588 100644 --- a/standalone/standalone/test/index.test.ts +++ b/standalone/standalone/test/index.test.ts @@ -78,16 +78,23 @@ describe('test/index.test.ts', () => { describe('multi instance prototype runner', () => { const fixturePath = path.join(__dirname, './fixtures/multi-callback-instance-module'); afterEach(async () => { - await fs.unlink(path.join(fixturePath, 'foo.log')); - await fs.unlink(path.join(fixturePath, 'bar.log')); + await fs.unlink(path.join(fixturePath, 'main', 'foo.log')); + await fs.unlink(path.join(fixturePath, 'main', 'bar.log')); + await fs.unlink(path.join(fixturePath, 'biz', 'fooBiz.log')); + await fs.unlink(path.join(fixturePath, 'biz', 'barBiz.log')); }); it('should work', async () => { await main(fixturePath); - const fooContent = await fs.readFile(path.join(fixturePath, 'foo.log'), 'utf8'); - const barContent = await fs.readFile(path.join(fixturePath, 'bar.log'), 'utf8'); + const fooContent = await fs.readFile(path.join(fixturePath, 'main', 'foo.log'), 'utf8'); + const barContent = await fs.readFile(path.join(fixturePath, 'main', 'bar.log'), 'utf8'); assert(fooContent.includes('hello, foo')); assert(barContent.includes('hello, bar')); + + const fooBizContent = await fs.readFile(path.join(fixturePath, 'biz', 'fooBiz.log'), 'utf8'); + const barBizContent = await fs.readFile(path.join(fixturePath, 'biz', 'barBiz.log'), 'utf8'); + assert(fooBizContent.includes('hello, foo biz')); + assert(barBizContent.includes('hello, bar biz')); }); });