Skip to content

Commit ad9acf9

Browse files
committed
fix(tegg): address module plugin review feedback
1 parent d2176e8 commit ad9acf9

62 files changed

Lines changed: 1425 additions & 427 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

tegg/core/aop-runtime/test/aop-runtime.test.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import path from 'node:path';
33
import { mock } from 'node:test';
44

55
import { CrosscutAdviceFactory } from '@eggjs/aop-decorator';
6-
import { EggPrototypeLifecycleUtil, LoadUnitFactory, LoadUnitLifecycleUtil } from '@eggjs/metadata';
7-
import { CoreTestHelper, EggTestContext } from '@eggjs/module-test-util';
8-
import { EggObjectLifecycleUtil, LoadUnitInstanceFactory } from '@eggjs/tegg-runtime';
6+
import { EggPrototypeLifecycleUtil, LoadUnitLifecycleUtil } from '@eggjs/metadata';
7+
import { CoreTestHelper, EggTestContext, LoaderUtil } from '@eggjs/module-test-util';
8+
import { EggObjectLifecycleUtil } from '@eggjs/tegg-runtime';
99
import type { LoadUnitInstance } from '@eggjs/tegg-types';
1010
import { describe, beforeEach, afterEach, it } from 'vitest';
1111

@@ -64,10 +64,7 @@ describe('test/aop-runtime.test.ts', () => {
6464
});
6565

6666
afterEach(async () => {
67-
for (const module of modules) {
68-
await LoadUnitFactory.destroyLoadUnit(module.loadUnit);
69-
await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);
70-
}
67+
await CoreTestHelper.destroyModules(modules);
7168
EggPrototypeLifecycleUtil.deleteLifecycle(eggPrototypeCrossCutHook);
7269
LoadUnitLifecycleUtil.deleteLifecycle(loadUnitAopHook);
7370
EggObjectLifecycleUtil.deleteLifecycle(eggObjectAopHook);
@@ -182,12 +179,23 @@ describe('test/aop-runtime.test.ts', () => {
182179
EggObjectLifecycleUtil.registerLifecycle(eggObjectAopHook);
183180
});
184181

182+
afterEach(() => {
183+
EggPrototypeLifecycleUtil.deleteLifecycle(eggPrototypeCrossCutHook);
184+
LoadUnitLifecycleUtil.deleteLifecycle(loadUnitAopHook);
185+
EggObjectLifecycleUtil.deleteLifecycle(eggObjectAopHook);
186+
});
187+
185188
it('should throw', async () => {
186-
await assert.rejects(async () => {
187-
await CoreTestHelper.prepareModules([
188-
path.join(__dirname, 'fixtures/modules/should_throw'),
189-
]);
190-
}, /Aop Advice\(PointcutAdvice\) not found in loadUnits/);
189+
const modulePath = path.join(__dirname, 'fixtures/modules/should_throw');
190+
const { innerObjectLoadUnitInstance } = await LoaderUtil.buildGlobalGraph([modulePath]);
191+
try {
192+
await assert.rejects(
193+
() => CoreTestHelper.getLoadUnitInstance(modulePath),
194+
/Aop Advice\(PointcutAdvice\) not found in loadUnits/,
195+
);
196+
} finally {
197+
await CoreTestHelper.destroyModules([innerObjectLoadUnitInstance]);
198+
}
191199
});
192200
});
193201

@@ -219,10 +227,7 @@ describe('test/aop-runtime.test.ts', () => {
219227
});
220228

221229
afterEach(async () => {
222-
for (const module of modules) {
223-
await LoadUnitFactory.destroyLoadUnit(module.loadUnit);
224-
await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);
225-
}
230+
await CoreTestHelper.destroyModules(modules);
226231
EggPrototypeLifecycleUtil.deleteLifecycle(eggPrototypeCrossCutHook);
227232
LoadUnitLifecycleUtil.deleteLifecycle(loadUnitAopHook);
228233
EggObjectLifecycleUtil.deleteLifecycle(eggObjectAopHook);

tegg/core/core-decorator/src/decorator/EggLifecycleProto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function EggLifecycleProto(params: CommonEggLifecycleProtoParams): Protot
2626
type EggLifecycleProtoDecoratorFactory = (params?: EggLifecycleProtoParams) => PrototypeDecorator;
2727

2828
const createLifecycleProto = (type: EggLifecycleType): EggLifecycleProtoDecoratorFactory => {
29-
return (params?: EggLifecycleProtoParams) => EggLifecycleProto({ type, ...params });
29+
return (params?: EggLifecycleProtoParams) => EggLifecycleProto({ ...params, type });
3030
};
3131

3232
export const LoadUnitLifecycleProto: EggLifecycleProtoDecoratorFactory = createLifecycleProto('LoadUnit');

tegg/core/core-decorator/test/inner-object-decorators.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ describe('core/core-decorator/test/inner-object-decorators.test.ts', () => {
123123
}, /EggLifecycle decorator should have type property/);
124124
});
125125

126+
it('should not let factory params override the fixed lifecycle type', () => {
127+
class FixedLoadUnitLifecycle {}
128+
LoadUnitLifecycleProto({ type: 'EggObject' } as any)(FixedLoadUnitLifecycle);
129+
130+
assert.deepEqual(PrototypeUtil.getEggLifecyclePrototypeMetadata(FixedLoadUnitLifecycle), {
131+
type: 'LoadUnit',
132+
});
133+
});
134+
126135
it('should return undefined metadata for non lifecycle proto', () => {
127136
assert.equal(PrototypeUtil.getEggLifecyclePrototypeMetadata(Router), undefined);
128137
});

tegg/core/dynamic-inject-runtime/test/index.test.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import assert from 'node:assert/strict';
22
import path from 'node:path';
33

4-
import { LoadUnitFactory } from '@eggjs/metadata';
54
import { EggTestContext, CoreTestHelper } from '@eggjs/module-test-util';
6-
import { type LoadUnitInstance, LoadUnitInstanceFactory } from '@eggjs/tegg-runtime';
5+
import { type LoadUnitInstance } from '@eggjs/tegg-runtime';
76
import { describe, it, beforeEach, afterEach } from 'vitest';
87

98
import { HelloService } from './fixtures/modules/dynamic-inject-module/HelloService.js';
@@ -18,10 +17,7 @@ describe('test/dynamic-inject-runtime.test.ts', () => {
1817
});
1918

2019
afterEach(async () => {
21-
for (const module of modules) {
22-
await LoadUnitFactory.destroyLoadUnit(module.loadUnit);
23-
await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);
24-
}
20+
await CoreTestHelper.destroyModules(modules);
2521
});
2622

2723
it('should work', async () => {

tegg/core/eventbus-runtime/test/EventBus.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { mock } from 'node:test';
44

55
import { PrototypeUtil } from '@eggjs/core-decorator';
66
import { EventInfoUtil, CORK_ID } from '@eggjs/eventbus-decorator';
7-
import { type EggPrototype, LoadUnitFactory } from '@eggjs/metadata';
7+
import { type EggPrototype } from '@eggjs/metadata';
88
import { CoreTestHelper, EggTestContext } from '@eggjs/module-test-util';
99
import { TimerUtil } from '@eggjs/tegg-common-util';
10-
import { type LoadUnitInstance, LoadUnitInstanceFactory } from '@eggjs/tegg-runtime';
10+
import { type LoadUnitInstance } from '@eggjs/tegg-runtime';
1111
import { describe, it, beforeEach, afterEach } from 'vitest';
1212

1313
import { EventContextFactory, EventHandlerFactory, SingletonEventBus } from '../src/index.ts';
@@ -26,10 +26,7 @@ describe('test/EventBus.test.ts', () => {
2626
});
2727

2828
afterEach(async () => {
29-
for (const module of modules) {
30-
await LoadUnitFactory.destroyLoadUnit(module.loadUnit);
31-
await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);
32-
}
29+
await CoreTestHelper.destroyModules(modules);
3330
mock.reset();
3431
});
3532

tegg/core/loader/src/LoaderFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class LoaderFactory {
125125
for (const clazz of clazzList) {
126126
// Inner object protos are also egg prototypes, so this branch must come first.
127127
if (PrototypeUtil.isEggInnerObject(clazz)) {
128-
res.innerObjectClazzList.push(clazz);
128+
res.innerObjectClazzList!.push(clazz);
129129
} else if (PrototypeUtil.isEggPrototype(clazz)) {
130130
res.clazzList.push(clazz);
131131
} else if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {

tegg/core/loader/test/LoaderInnerObject.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('core/loader/test/LoaderInnerObject.test.ts', () => {
1919
it('should divert inner object clazz to innerObjectClazzList', async () => {
2020
const [descriptor] = await LoaderFactory.loadApp([moduleRef]);
2121

22-
const innerNames = descriptor.innerObjectClazzList.map((t) => t.name).sort();
22+
const innerNames = descriptor.innerObjectClazzList!.map((t) => t.name).sort();
2323
assert.deepEqual(innerNames, ['ControllerHook', 'FetchRouter']);
2424

2525
// Inner object classes must NOT stay in clazzList.

tegg/core/metadata/src/model/ModuleDescriptor.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface ModuleDescriptor {
1212
optional?: boolean;
1313
clazzList: EggProtoImplClass[];
1414
multiInstanceClazzList: EggProtoImplClass[];
15-
innerObjectClazzList: EggProtoImplClass[];
15+
innerObjectClazzList?: EggProtoImplClass[];
1616
protos: ProtoDescriptor[];
1717
}
1818

@@ -39,7 +39,7 @@ export class ModuleDescriptorDumper {
3939
return ModuleDescriptorDumper.stringifyClazz(t, moduleDescriptor);
4040
})
4141
.join(',')}],` +
42-
`"innerObjectClazzList": [${moduleDescriptor.innerObjectClazzList
42+
`"innerObjectClazzList": [${(moduleDescriptor.innerObjectClazzList ?? [])
4343
.map((t) => {
4444
return ModuleDescriptorDumper.stringifyClazz(t, moduleDescriptor);
4545
})
@@ -86,7 +86,7 @@ export class ModuleDescriptorDumper {
8686
for (const clazz of desc.multiInstanceClazzList) addClazz(clazz);
8787
// Inner object / lifecycle proto classes are diverted out of clazzList, but
8888
// their files must still be recorded so bundle mode re-imports them.
89-
for (const clazz of desc.innerObjectClazzList) addClazz(clazz);
89+
for (const clazz of desc.innerObjectClazzList ?? []) addClazz(clazz);
9090
return Array.from(fileSet);
9191
}
9292

@@ -98,6 +98,7 @@ export class ModuleDescriptorDumper {
9898
const tmpPath = path.join(tmpDir, path.basename(dumpPath));
9999
try {
100100
await fs.writeFile(tmpPath, ModuleDescriptorDumper.stringifyDescriptor(desc));
101+
await fs.rm(dumpPath, { force: true });
101102
await fs.rename(tmpPath, dumpPath);
102103
} finally {
103104
await fs.rm(tmpDir, { recursive: true, force: true });

tegg/core/metadata/src/model/graph/GlobalGraph.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export class GlobalGraph {
6060
moduleProtoDescriptorMap: Map<string, ProtoDescriptor[]>;
6161
strict: boolean;
6262
private buildHooks: GlobalGraphBuildHook[];
63+
#buildState: 'created' | 'building' | 'built' = 'created';
6364
/** Lazily built proto-name index for dependency resolution; invalidated on vertex changes. */
6465
#protoNameIndex: ProtoNameIndex | null = null;
6566

@@ -94,31 +95,42 @@ export class GlobalGraph {
9495
}
9596

9697
registerBuildHook(hook: GlobalGraphBuildHook): void {
98+
if (this.#buildState !== 'created') {
99+
throw new Error(`cannot register global graph build hook after build has started (state: ${this.#buildState})`);
100+
}
97101
this.buildHooks.push(hook);
98102
}
99103

100104
addModuleNode(moduleNode: GlobalModuleNode): void {
101105
if (!this.moduleGraph.addVertex(new GraphNode<GlobalModuleNode, ModuleDependencyMeta>(moduleNode))) {
102106
throw new Error(`duplicate module: ${moduleNode}`);
103107
}
108+
this.#protoNameIndex = null;
104109
for (const protoNode of moduleNode.protos) {
105110
if (!this.protoGraph.addVertex(protoNode)) {
106111
throw new Error(`duplicate proto: ${protoNode.val}`);
107112
}
108113
}
109-
this.#protoNameIndex = null;
110114
}
111115

112116
build(): void {
113-
for (const moduleNode of this.moduleGraph.nodes.values()) {
114-
for (const protoNode of moduleNode.val.protos) {
115-
for (const injectObj of protoNode.val.proto.injectObjects) {
116-
this.buildInjectEdge(moduleNode, protoNode, injectObj);
117+
if (this.#buildState !== 'created') {
118+
throw new Error(`global graph can only be built once (state: ${this.#buildState})`);
119+
}
120+
this.#buildState = 'building';
121+
try {
122+
for (const moduleNode of this.moduleGraph.nodes.values()) {
123+
for (const protoNode of moduleNode.val.protos) {
124+
for (const injectObj of protoNode.val.proto.injectObjects) {
125+
this.buildInjectEdge(moduleNode, protoNode, injectObj);
126+
}
117127
}
118128
}
119-
}
120-
for (const buildHook of this.buildHooks) {
121-
buildHook(this);
129+
for (const buildHook of this.buildHooks) {
130+
buildHook(this);
131+
}
132+
} finally {
133+
this.#buildState = 'built';
122134
}
123135
}
124136

tegg/core/metadata/test/GlobalGraph.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,65 @@ describe('test/LoadUnit/GlobalGraph.test.ts', () => {
184184
['logger', 'bar', 'constructorBase', 'fooConstructor', 'fooConstructorLogger'],
185185
);
186186
});
187+
188+
it('should reject late build hooks and repeated builds', () => {
189+
const graph = new GlobalGraph();
190+
let hookCalls = 0;
191+
graph.registerBuildHook(() => hookCalls++);
192+
193+
graph.build();
194+
195+
assert.equal(hookCalls, 1);
196+
assert.throws(
197+
() => graph.registerBuildHook(() => {}),
198+
/cannot register global graph build hook after build has started/,
199+
);
200+
assert.throws(() => graph.build(), /global graph can only be built once/);
201+
});
202+
203+
it('should reject build hook registration while build hooks are running', () => {
204+
const graph = new GlobalGraph();
205+
let nestedHookCalled = false;
206+
graph.registerBuildHook(() => {
207+
graph.registerBuildHook(() => {
208+
nestedHookCalled = true;
209+
});
210+
});
211+
212+
assert.throws(
213+
() => graph.build(),
214+
/cannot register global graph build hook after build has started \(state: building\)/,
215+
);
216+
assert.equal(nestedHookCalled, false);
217+
assert.throws(() => graph.build(), /global graph can only be built once \(state: built\)/);
218+
});
219+
220+
it('should invalidate the proto name index before a partial add failure', () => {
221+
const graph = new GlobalGraph();
222+
const consumer = createProtoDescriptor({ name: 'consumer' });
223+
const existing = createProtoDescriptor({ name: 'existing' });
224+
const initialNode = new GlobalModuleNode({ name: 'initial', unitPath: '/fixtures/initial', optional: false });
225+
initialNode.addProto(consumer);
226+
initialNode.addProto(existing);
227+
graph.addModuleNode(initialNode);
228+
229+
assert(graph.findDependencyProtoNode(consumer, { refName: 'existing', objName: 'existing', qualifiers: [] }));
230+
231+
const addedBeforeFailure = createProtoDescriptor({ name: 'addedBeforeFailure', moduleName: 'partial' });
232+
const duplicate = createProtoDescriptor({ name: 'existing', moduleName: 'partial' });
233+
duplicate.instanceModuleName = existing.instanceModuleName;
234+
duplicate.instanceDefineUnitPath = existing.instanceDefineUnitPath;
235+
const partialNode = new GlobalModuleNode({ name: 'partial', unitPath: '/fixtures/partial', optional: false });
236+
partialNode.addProto(addedBeforeFailure);
237+
partialNode.addProto(duplicate);
238+
239+
assert.throws(() => graph.addModuleNode(partialNode), /duplicate proto/);
240+
assert(
241+
graph.findDependencyProtoNode(consumer, {
242+
refName: 'addedBeforeFailure',
243+
objName: 'addedBeforeFailure',
244+
qualifiers: [],
245+
}),
246+
);
247+
});
187248
});

0 commit comments

Comments
 (0)