Skip to content

Commit

Permalink
fix: fix use MultiInstanceProto from other modules
Browse files Browse the repository at this point in the history
  • Loading branch information
killagu committed Aug 29, 2023
1 parent 5bf0b2a commit fa92d9b
Show file tree
Hide file tree
Showing 18 changed files with 368 additions and 217 deletions.
6 changes: 1 addition & 5 deletions core/core-decorator/src/util/PrototypeUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,11 @@ export class PrototypeUtil {
return metadata;
}
const callBackMetadata = MetadataUtil.getMetaData<EggMultiInstanceCallbackPrototypeInfo>(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;
}
}

Expand Down
2 changes: 2 additions & 0 deletions core/metadata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
28 changes: 28 additions & 0 deletions core/metadata/src/impl/LoadUnitMultiInstanceProtoHook.ts
Original file line number Diff line number Diff line change
@@ -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<LoadUnitLifecycleContext, LoadUnit> {
multiInstanceClazzSet: Set<EggProtoImplClass> = new Set();

async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {
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);
}
}
}
}

254 changes: 254 additions & 0 deletions core/metadata/src/model/AppGraph.ts
Original file line number Diff line number Diff line change
@@ -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<ModuleNode>;
ownerModule: GraphNode<ModuleNode>;
}

export type ClazzMetaMap = Record<EggPrototypeName, InstanceClazzMeta[]>;

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<ModuleNode>) {
this.build(graph);
}

private build(graph: Graph<ModuleNode>) {
/**
* 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<ModuleNode>): GraphNode<ModuleNode>[] {
const result: Set<GraphNode<ModuleNode>> = 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',

Check warning on line 150 in core/metadata/src/model/AppGraph.ts

View check run for this annotation

Codecov / codecov/patch

core/metadata/src/model/AppGraph.ts#L150

Added line #L150 was not covered by tests
objName,
properQualifiers,
mayObjs.map(t => {
return t.instanceModule.val.moduleConfig.path;

Check warning on line 154 in core/metadata/src/model/AppGraph.ts

View check run for this annotation

Codecov / codecov/patch

core/metadata/src/model/AppGraph.ts#L153-L154

Added lines #L153 - L154 were not covered by tests
}));
throw new Error(message);

Check warning on line 156 in core/metadata/src/model/AppGraph.ts

View check run for this annotation

Codecov / codecov/patch

core/metadata/src/model/AppGraph.ts#L156

Added line #L156 was not covered by tests
}

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<ModuleNode>;
private clazzMap: ClazzMap;
moduleConfigList: Array<ModuleReference>;

constructor() {
this.graph = new Graph<ModuleNode>();
}

addNode(moduleNode: ModuleNode) {
if (!this.graph.addVertex(new GraphNode(moduleNode))) {
throw new Error(`duplicate module: ${moduleNode}`);

Check warning on line 218 in core/metadata/src/model/AppGraph.ts

View check run for this annotation

Codecov / codecov/patch

core/metadata/src/model/AppGraph.ts#L218

Added line #L218 was not covered by tests
}
}

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);
}
}
24 changes: 1 addition & 23 deletions plugin/tegg/lib/EggModuleLoader.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand Down
26 changes: 3 additions & 23 deletions standalone/standalone/src/EggModuleLoader.ts
Original file line number Diff line number Diff line change
@@ -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[];
Expand All @@ -17,28 +11,14 @@ export class EggModuleLoader {
}

private buildAppGraph(loaderCache: Map<string, Loader>) {
const appGraph = new StandaloneGraph();
const appGraph = new AppGraph();
for (const moduleConfig of this.moduleReferences) {
const modulePath = moduleConfig.path;
const moduleNode = new ModuleNode(moduleConfig);
const loader = LoaderFactory.createLoader(modulePath, EggLoadUnitType.MODULE);
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);
Expand Down
Loading

0 comments on commit fa92d9b

Please sign in to comment.