Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix use MultiInstanceProto from other modules #147

Merged
merged 1 commit into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -120,7 +120,7 @@
MetadataUtil.defineMetaData(this.MULTI_INSTANCE_PROTOTYPE_CALLBACK_PROPERTY, property, clazz);
}

/**

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-ubuntu (14)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-ubuntu (16)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-ubuntu (18)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-ubuntu (20)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-windows (14)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-windows (16)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-windows (18)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-windows (20)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-macos (14)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-macos (16)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-macos (18)

JSDoc @return declaration present but return expression not available in function

Check warning on line 123 in core/core-decorator/src/util/PrototypeUtil.ts

View workflow job for this annotation

GitHub Actions / Runner-macos (20)

JSDoc @return declaration present but return expression not available in function
* get class property
* @param {EggProtoImplClass} clazz -
* @param {MultiInstancePrototypeGetObjectsContext} ctx -
Expand All @@ -132,15 +132,11 @@
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
Loading