Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
fe0d3d4
feat(core): add module plugin core mechanism
gxkl Jul 4, 2026
90fe823
feat(standalone): two-phase StandaloneApp with module plugin support
gxkl Jul 4, 2026
bad4f25
feat(tegg-plugin): instantiate InnerObjectLoadUnit in app mode
gxkl Jul 4, 2026
a1ad0eb
feat(tegg): convert built-in framework hooks to module plugins
gxkl Jul 4, 2026
d941ee4
docs(wiki): record tegg module plugin architecture
gxkl Jul 4, 2026
bb28f2f
refactor(tegg): address module-plugin review - dedupe hook, untangle …
gxkl Jul 5, 2026
a97ca3e
refactor(tegg): built-in framework hooks load through the module scan
gxkl Jul 5, 2026
97851ed
refactor(tegg): decentralize module plugins - enabling the plugin IS …
gxkl Jul 5, 2026
8d902f9
refactor(tegg): the aop PLUGIN is the aop module; provided objects jo…
gxkl Jul 5, 2026
2afbd54
refactor(runtime): provided inner objects are ordinary protos end to end
gxkl Jul 5, 2026
f63b0a8
chore(aop-runtime): drop leftover InnerObjects re-export
gxkl Jul 5, 2026
bb0e787
refactor(tegg): discovery stays deps/module.json-declared; drop enabl…
gxkl Jul 5, 2026
5d1d9af
refactor(runtime): name the provided-instance factory for what it is
gxkl Jul 5, 2026
8c80e59
docs(aop): explain why AopContextHook stays imperatively registered
gxkl Jul 5, 2026
52bed86
docs(tegg): explain why inner objects use provided instances, not egg…
gxkl Jul 5, 2026
e1afbef
docs(tegg): state why ConfigSourceLoadUnitHook is a host built-in
gxkl Jul 5, 2026
541a032
refactor(tegg): ConfigSourceLoadUnitHook lives in @eggjs/tegg-config,…
gxkl Jul 5, 2026
14b9ad6
fix(tegg): gate optional modules' hooks; fix the enabled-promotion match
gxkl Jul 5, 2026
6f51f31
revert(tegg-config): framework discovery reads package.json only
gxkl Jul 5, 2026
b2d71ce
refactor(standalone): defer module scan out of the StandaloneApp cons…
gxkl Jul 5, 2026
7712d67
refactor(standalone): drop deprecated StandaloneAppOptions.innerObjects
gxkl Jul 5, 2026
86ed7f6
refactor(standalone): keep innerObjects assembly in the constructor, …
gxkl Jul 5, 2026
7c95e76
refactor(standalone): make runtimeConfig a placeholder filled at init
gxkl Jul 5, 2026
a219f1d
feat(standalone): accept a logger via StandaloneAppOptions
gxkl Jul 5, 2026
930826f
refactor(standalone): make StandaloneApp.init idempotent
gxkl Jul 5, 2026
8140576
fix(standalone): keep host moduleConfig handler override semantics
gxkl Jul 5, 2026
7be195f
refactor(standalone): build framework base inner objects at the point…
gxkl Jul 5, 2026
4e7dcbb
refactor(standalone): align StandaloneApp API with tegg#325
gxkl Jul 5, 2026
8034122
refactor(standalone): discover builtin framework plugins via own pack…
gxkl Jul 5, 2026
044e7d9
refactor(standalone): move dal manager cleanup into the dal module it…
gxkl Jul 5, 2026
4bdda2c
feat(dal): declare the mysqlDataSourceManager injection surface in th…
gxkl Jul 5, 2026
c5f56ce
fix(dal): declare manager cleanup via @LifecycleDestroy
gxkl Jul 5, 2026
eddaf9c
refactor(dal): type MysqlDataSourceManagerObject via declaration merging
gxkl Jul 5, 2026
8096f55
fix(standalone): allowlist scan-declaration deps for unplugin-unused
gxkl Jul 5, 2026
060167d
fix(core-decorator): explicit return types for isolatedDeclarations
gxkl Jul 5, 2026
f38bfad
fix(config): default the framework module scan to egg
gxkl Jul 5, 2026
4a47fa7
refactor(config): resolve the framework dir with getFrameworkPath
gxkl Jul 5, 2026
1159258
fix(config): tolerate non-materialized module dirs from bundle manifests
gxkl Jul 5, 2026
1e4ffba
fix(tegg): JSON-safe descriptor dump and bundle-tolerant module confi…
gxkl Jul 5, 2026
9d904d5
test(tegg): explicit boot-hook timeouts for slow Windows runners
gxkl Jul 5, 2026
cd2a241
test(tegg): align boot-hook timeouts with the root config values
gxkl Jul 5, 2026
555cfcb
fix(loader): remove redundant test glob excludes
gxkl Jul 9, 2026
04365fd
chore(tegg-config): debug framework resolution failures
gxkl Jul 9, 2026
a759f50
chore(tegg): remove empty plugin boot hooks
gxkl Jul 9, 2026
7f0debe
fix(metadata): emit valid module descriptor JSON
gxkl Jul 9, 2026
7a44edc
fix(standalone): pass framework deps during preload
gxkl Jul 9, 2026
73204be
fix(standalone): await app destroy on success
gxkl Jul 9, 2026
20cc7fd
fix(standalone): reject removed innerObjects option
gxkl Jul 9, 2026
c4d249b
docs(tegg): correct module plugin feeding rules
gxkl Jul 9, 2026
5f9de5b
fix(standalone): reuse manifest module references
gxkl Jul 9, 2026
e58bbfb
fix(standalone): let provided inner objects override defaults
gxkl Jul 9, 2026
f86cb73
fix(tegg): narrow lifecycle proto types
gxkl Jul 9, 2026
da6f2cb
fix(tegg): clean partial module init failures
gxkl Jul 9, 2026
ee6212e
refactor(tegg): share tolerant module config resolution
gxkl Jul 9, 2026
695f15a
test(tegg): cover inner object multi-app isolation
gxkl Jul 9, 2026
7f8cfad
fix(tegg): match module plugins by package
gxkl Jul 9, 2026
ee85741
refactor(tegg): share manifest data builder
gxkl Jul 9, 2026
bd79a6c
fix(tegg-config): scan parent framework modules
gxkl Jul 9, 2026
847f8b8
fix(tegg): qualify inner objects by defining module
gxkl Jul 9, 2026
6afd983
refactor(aop): register context hook as inner object
gxkl Jul 9, 2026
525a91f
refactor(dal): inject mysql data source manager
gxkl Jul 9, 2026
6c84283
refactor(metadata): reuse prototype builder for inner objects
gxkl Jul 9, 2026
2fcc7e7
test(standalone): expect destroy lifecycle after main
gxkl Jul 9, 2026
1ad38c0
fix(tegg-config): keep nearest duplicate framework modules
gxkl Jul 9, 2026
7bed7a7
fix(tegg-config): stop at boolean framework roots
gxkl Jul 9, 2026
ba38856
fix(standalone): clarify inner object override warnings
gxkl Jul 9, 2026
d132713
test(tegg-config): cover framework module dedupe edges
gxkl Jul 9, 2026
b3edc3f
refactor(dal): inject table and sql map managers
gxkl Jul 9, 2026
fad5597
fix(tegg-config): resolve framework module json packages locally
gxkl Jul 9, 2026
ec775a3
docs(standalone): update inner object handler option
gxkl Jul 9, 2026
bcc790a
fix(tegg): address review follow-ups
gxkl Jul 9, 2026
e9b69ea
fix(standalone): do not await ctx.destroy on the request path
gxkl Jul 10, 2026
a31d579
fix(tegg-config): compare realpaths when detecting duplicate modules
gxkl Jul 10, 2026
b7a6718
fix(tegg): address module plugin review feedback
gxkl Jul 12, 2026
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
2 changes: 2 additions & 0 deletions tegg/core/aop-decorator/src/CrosscutAdviceFactory.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import assert from 'node:assert';

import { InnerObjectProto } from '@eggjs/core-decorator';
import type { EggProtoImplClass, IAdvice, AdviceInfo } from '@eggjs/tegg-types';

import { CrosscutInfoUtil } from './util/index.ts';

@InnerObjectProto()
export class CrosscutAdviceFactory {
private readonly crosscutAdviceClazzList: Array<EggProtoImplClass<IAdvice>> = [];

Expand Down
4 changes: 1 addition & 3 deletions tegg/core/aop-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"dependencies": {
"@eggjs/aop-decorator": "workspace:*",
"@eggjs/core-decorator": "workspace:*",
"@eggjs/lifecycle": "workspace:*",
"@eggjs/metadata": "workspace:*",
"@eggjs/tegg-common-util": "workspace:*",
"@eggjs/tegg-runtime": "workspace:*",
Expand All @@ -59,8 +60,5 @@
},
"engines": {
"node": ">=22.18.0"
},
"eggModule": {
"name": "teggAopRuntime"
}
}
27 changes: 27 additions & 0 deletions tegg/core/aop-runtime/src/AopContextAdviceRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { InnerObjectProto } from '@eggjs/core-decorator';
import { ObjectInitType } from '@eggjs/tegg-types';
import type { EggPrototype } from '@eggjs/tegg-types';

export interface ProtoToCreate {
name: string;
proto: EggPrototype;
}

@InnerObjectProto()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
export class AopContextAdviceRegistry {
readonly #requestProtoList: ProtoToCreate[] = [];

addAdvice(name: string, proto: EggPrototype): void {
if (proto.initType !== ObjectInitType.CONTEXT) {
return;
}
if (this.#requestProtoList.some((t) => t.name === name && t.proto === proto)) {
return;
}
this.#requestProtoList.push({ name, proto });
}

getRequestProtos(): readonly ProtoToCreate[] {
return this.#requestProtoList;
}
}
27 changes: 27 additions & 0 deletions tegg/core/aop-runtime/src/AopGraphHookRegistrar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { InnerObjectProto } from '@eggjs/core-decorator';
import { LifecyclePostInject } from '@eggjs/lifecycle';
import { GlobalGraph } from '@eggjs/metadata';

import { crossCutGraphHook } from './CrossCutGraphHook.js';
import { pointCutGraphHook } from './PointCutGraphHook.js';
Comment thread
gxkl marked this conversation as resolved.

/**
* Registers the AOP graph build hooks declaratively. Instantiated with the
* InnerObjectLoadUnit, which both hosts run AFTER the business GlobalGraph is
* created and BEFORE build() consumes the hooks — the only valid window.
*/
@InnerObjectProto()
export class AopGraphHookRegistrar {
@LifecyclePostInject()
protected registerGraphHooks(): void {
const globalGraph = GlobalGraph.instance;
if (!globalGraph) {
throw new Error(
'[aop-runtime] GlobalGraph must be created before AopGraphHookRegistrar is instantiated, ' +
'cross-loadUnit crosscut/pointcut weaving would silently never happen',
);
}
globalGraph.registerBuildHook(crossCutGraphHook);
globalGraph.registerBuildHook(pointCutGraphHook);
}
}
3 changes: 2 additions & 1 deletion tegg/core/aop-runtime/src/EggObjectAopHook.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import assert from 'node:assert';

import { Aspect } from '@eggjs/aop-decorator';
import { PrototypeUtil } from '@eggjs/core-decorator';
import { EggObjectLifecycleProto, PrototypeUtil } from '@eggjs/core-decorator';
import { EggContainerFactory } from '@eggjs/tegg-runtime';
import { ASPECT_LIST, InjectType } from '@eggjs/tegg-types';
import type { EggObject, EggObjectLifeCycleContext, LifecycleHook } from '@eggjs/tegg-types';

import { AspectExecutor } from './AspectExecutor.js';

@EggObjectLifecycleProto()
export class EggObjectAopHook implements LifecycleHook<EggObjectLifeCycleContext, EggObject> {
private hijackMethods(obj: any, aspectList: Array<Aspect>) {
for (const aspect of aspectList) {
Expand Down
7 changes: 3 additions & 4 deletions tegg/core/aop-runtime/src/EggPrototypeCrossCutHook.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { CrosscutAdviceFactory, CrosscutInfoUtil } from '@eggjs/aop-decorator';
import { EggPrototypeLifecycleProto, Inject } from '@eggjs/core-decorator';
import type { EggPrototype, EggPrototypeLifecycleContext, LifecycleHook } from '@eggjs/tegg-types';

@EggPrototypeLifecycleProto()
export class EggPrototypeCrossCutHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {
@Inject()
private readonly crosscutAdviceFactory: CrosscutAdviceFactory;

constructor(crosscutAdviceFactory: CrosscutAdviceFactory) {
this.crosscutAdviceFactory = crosscutAdviceFactory;
}

async preCreate(ctx: EggPrototypeLifecycleContext): Promise<void> {
if (CrosscutInfoUtil.isCrosscutAdvice(ctx.clazz)) {
this.crosscutAdviceFactory.registerCrossAdviceClazz(ctx.clazz);
Expand Down
11 changes: 8 additions & 3 deletions tegg/core/aop-runtime/src/LoadUnitAopHook.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AspectInfoUtil, AspectMetaBuilder, CrosscutAdviceFactory } from '@eggjs/aop-decorator';
import { Inject, LoadUnitLifecycleProto } from '@eggjs/core-decorator';
import { EggPrototypeFactory, TeggError } from '@eggjs/metadata';
import type {
EggPrototype,
Expand All @@ -8,12 +9,15 @@ import type {
LoadUnitLifecycleContext,
} from '@eggjs/tegg-types';

import { AopContextAdviceRegistry } from './AopContextAdviceRegistry.js';

@LoadUnitLifecycleProto()
export class LoadUnitAopHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {
@Inject()
private readonly crosscutAdviceFactory: CrosscutAdviceFactory;

constructor(crosscutAdviceFactory: CrosscutAdviceFactory) {
this.crosscutAdviceFactory = crosscutAdviceFactory;
}
@Inject()
private readonly aopContextAdviceRegistry: AopContextAdviceRegistry;

async postCreate(_: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {
for (const proto of loadUnit.iterateEggPrototype()) {
Expand All @@ -39,6 +43,7 @@ export class LoadUnitAopHook implements LifecycleHook<LoadUnitLifecycleContext,
qualifiers: [],
proto: adviceProto as EggPrototype,
});
this.aopContextAdviceRegistry.addAdvice(advice.name, adviceProto as EggPrototype);
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions tegg/core/aop-runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from './AopContextAdviceRegistry.js';
export * from './AspectExecutor.js';
export * from './CrossCutGraphHook.js';
export * from './EggObjectAopHook.js';
export * from './EggPrototypeCrossCutHook.js';
export * from './LoadUnitAopHook.js';
export * from './PointCutGraphHook.js';
export * from './AopGraphHookRegistrar.js';
2 changes: 2 additions & 0 deletions tegg/core/aop-runtime/test/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

exports[`should export stable 1`] = `
{
"AopContextAdviceRegistry": [Function],
"AopGraphHookRegistrar": [Function],
"AspectExecutor": [Function],
"EggObjectAopHook": [Function],
"EggPrototypeCrossCutHook": [Function],
Expand Down
63 changes: 38 additions & 25 deletions tegg/core/aop-runtime/test/aop-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import path from 'node:path';
import { mock } from 'node:test';

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

// must import before other imports
import { Hello } from './fixtures/modules/hello_succeed/Hello.js';

import { crossCutGraphHook } from '../src/CrossCutGraphHook.js';
import { AopContextAdviceRegistry } from '../src/AopContextAdviceRegistry.js';
import { EggObjectAopHook } from '../src/EggObjectAopHook.js';
import { EggPrototypeCrossCutHook } from '../src/EggPrototypeCrossCutHook.js';
import { LoadUnitAopHook } from '../src/LoadUnitAopHook.js';
Expand All @@ -22,6 +23,13 @@ import { CallTrace } from './fixtures/modules/hello_cross_cut/CallTrace.js';
import { crosscutAdviceParams } from './fixtures/modules/hello_cross_cut/HelloCrossCut.js';
import { pointcutAdviceParams } from './fixtures/modules/hello_point_cut/HelloPointCut.js';

function createLoadUnitAopHook(crosscutAdviceFactory: CrosscutAdviceFactory): LoadUnitAopHook {
const loadUnitAopHook = new LoadUnitAopHook();
Reflect.set(loadUnitAopHook, 'crosscutAdviceFactory', crosscutAdviceFactory);
Reflect.set(loadUnitAopHook, 'aopContextAdviceRegistry', new AopContextAdviceRegistry());
return loadUnitAopHook;
}

describe('test/aop-runtime.test.ts', () => {
afterEach(() => {
mock.reset();
Expand All @@ -37,15 +45,15 @@ describe('test/aop-runtime.test.ts', () => {
beforeEach(async () => {
crosscutAdviceFactory = new CrosscutAdviceFactory();
eggObjectAopHook = new EggObjectAopHook();
loadUnitAopHook = new LoadUnitAopHook(crosscutAdviceFactory);
eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(crosscutAdviceFactory);
loadUnitAopHook = createLoadUnitAopHook(crosscutAdviceFactory);
eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook();
Reflect.set(eggPrototypeCrossCutHook, 'crosscutAdviceFactory', crosscutAdviceFactory);
EggPrototypeLifecycleUtil.registerLifecycle(eggPrototypeCrossCutHook);
LoadUnitLifecycleUtil.registerLifecycle(loadUnitAopHook);
EggObjectLifecycleUtil.registerLifecycle(eggObjectAopHook);

modules = await CoreTestHelper.prepareModules(
[
path.join(__dirname, '..'),
path.join(__dirname, 'fixtures/modules/hello_succeed'),
path.join(__dirname, 'fixtures/modules/hello_point_cut'),
path.join(__dirname, 'fixtures/modules/state_point_cut'),
Expand All @@ -56,10 +64,7 @@ describe('test/aop-runtime.test.ts', () => {
});

afterEach(async () => {
for (const module of modules) {
await LoadUnitFactory.destroyLoadUnit(module.loadUnit);
await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);
}
await CoreTestHelper.destroyModules(modules);
EggPrototypeLifecycleUtil.deleteLifecycle(eggPrototypeCrossCutHook);
LoadUnitLifecycleUtil.deleteLifecycle(loadUnitAopHook);
EggObjectLifecycleUtil.deleteLifecycle(eggObjectAopHook);
Expand Down Expand Up @@ -166,20 +171,31 @@ describe('test/aop-runtime.test.ts', () => {
beforeEach(async () => {
crosscutAdviceFactory = new CrosscutAdviceFactory();
eggObjectAopHook = new EggObjectAopHook();
loadUnitAopHook = new LoadUnitAopHook(crosscutAdviceFactory);
eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(crosscutAdviceFactory);
loadUnitAopHook = createLoadUnitAopHook(crosscutAdviceFactory);
eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook();
Reflect.set(eggPrototypeCrossCutHook, 'crosscutAdviceFactory', crosscutAdviceFactory);
EggPrototypeLifecycleUtil.registerLifecycle(eggPrototypeCrossCutHook);
LoadUnitLifecycleUtil.registerLifecycle(loadUnitAopHook);
EggObjectLifecycleUtil.registerLifecycle(eggObjectAopHook);
});

afterEach(() => {
EggPrototypeLifecycleUtil.deleteLifecycle(eggPrototypeCrossCutHook);
LoadUnitLifecycleUtil.deleteLifecycle(loadUnitAopHook);
EggObjectLifecycleUtil.deleteLifecycle(eggObjectAopHook);
});

it('should throw', async () => {
await assert.rejects(async () => {
await CoreTestHelper.prepareModules([
path.join(__dirname, '..'),
path.join(__dirname, 'fixtures/modules/should_throw'),
]);
}, /Aop Advice\(PointcutAdvice\) not found in loadUnits/);
const modulePath = path.join(__dirname, 'fixtures/modules/should_throw');
const { innerObjectLoadUnitInstance } = await LoaderUtil.buildGlobalGraph([modulePath]);
try {
await assert.rejects(
() => CoreTestHelper.getLoadUnitInstance(modulePath),
/Aop Advice\(PointcutAdvice\) not found in loadUnits/,
);
} finally {
await CoreTestHelper.destroyModules([innerObjectLoadUnitInstance]);
}
});
});

Expand All @@ -193,15 +209,15 @@ describe('test/aop-runtime.test.ts', () => {
beforeEach(async () => {
crosscutAdviceFactory = new CrosscutAdviceFactory();
eggObjectAopHook = new EggObjectAopHook();
loadUnitAopHook = new LoadUnitAopHook(crosscutAdviceFactory);
eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(crosscutAdviceFactory);
loadUnitAopHook = createLoadUnitAopHook(crosscutAdviceFactory);
eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook();
Reflect.set(eggPrototypeCrossCutHook, 'crosscutAdviceFactory', crosscutAdviceFactory);
EggPrototypeLifecycleUtil.registerLifecycle(eggPrototypeCrossCutHook);
LoadUnitLifecycleUtil.registerLifecycle(loadUnitAopHook);
EggObjectLifecycleUtil.registerLifecycle(eggObjectAopHook);

modules = await CoreTestHelper.prepareModules(
[
path.join(__dirname, '..'),
path.join(__dirname, 'fixtures/modules/constructor_inject_aop'),
path.join(__dirname, 'fixtures/modules/hello_point_cut'),
path.join(__dirname, 'fixtures/modules/hello_cross_cut'),
Expand All @@ -211,10 +227,7 @@ describe('test/aop-runtime.test.ts', () => {
});

afterEach(async () => {
for (const module of modules) {
await LoadUnitFactory.destroyLoadUnit(module.loadUnit);
await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);
}
await CoreTestHelper.destroyModules(modules);
EggPrototypeLifecycleUtil.deleteLifecycle(eggPrototypeCrossCutHook);
LoadUnitLifecycleUtil.deleteLifecycle(loadUnitAopHook);
EggObjectLifecycleUtil.deleteLifecycle(eggObjectAopHook);
Expand Down
Loading
Loading