From cb79e792ff23d1d458796e2f5b603334f1d00887 Mon Sep 17 00:00:00 2001 From: Ydream <995862798@qq.com> Date: Thu, 21 Dec 2023 19:54:24 +0800 Subject: [PATCH 1/4] feat: allow a handler to subscribe to multiple events --- core/eventbus-decorator/README.md | 11 +++++++++++ core/eventbus-decorator/src/Event.ts | 2 +- core/eventbus-decorator/src/EventInfoUtil.ts | 9 +++++---- core/eventbus-decorator/test/Event.test.ts | 4 ++-- .../test/fixtures/right-event-handle.ts | 2 ++ .../src/EventHandlerFactory.ts | 8 +++++--- core/eventbus-runtime/test/EventBus.test.ts | 12 ++++++------ .../eventbus/lib/EventHandlerProtoManager.ts | 4 ++-- plugin/eventbus/test/eventbus.test.ts | 19 +++++++++++++++++++ .../app/event-module/HelloService.ts | 5 +++++ .../app/event-module/MultiEventHandler.ts | 8 ++++++++ 11 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts diff --git a/core/eventbus-decorator/README.md b/core/eventbus-decorator/README.md index 1c8be6d8..3a199e51 100644 --- a/core/eventbus-decorator/README.md +++ b/core/eventbus-decorator/README.md @@ -56,3 +56,14 @@ export class Foo { } } ``` + +### handle multiple event +```ts +@Event('hello') +@Event('hi') +export class Foo { + async handle(msg: string): Promise { + console.log('msg: ', msg); + } +} +``` diff --git a/core/eventbus-decorator/src/Event.ts b/core/eventbus-decorator/src/Event.ts index 377e8e2b..2e3096be 100644 --- a/core/eventbus-decorator/src/Event.ts +++ b/core/eventbus-decorator/src/Event.ts @@ -8,7 +8,7 @@ import { Events } from '@eggjs/tegg'; export function Event(eventName: E) { return function(clazz: new () => EventHandler) { - EventInfoUtil.setEventName(eventName, clazz); + EventInfoUtil.addEventName(eventName, clazz); const func = SingletonProto({ accessLevel: AccessLevel.PUBLIC, }); diff --git a/core/eventbus-decorator/src/EventInfoUtil.ts b/core/eventbus-decorator/src/EventInfoUtil.ts index 11f8f168..bbc32c3e 100644 --- a/core/eventbus-decorator/src/EventInfoUtil.ts +++ b/core/eventbus-decorator/src/EventInfoUtil.ts @@ -4,11 +4,12 @@ import { EventName } from './EventBus'; export const EVENT_NAME = Symbol.for('EggPrototype#eventName'); export class EventInfoUtil { - static setEventName(eventName: EventName, clazz: EggProtoImplClass) { - MetadataUtil.defineMetaData(EVENT_NAME, eventName, clazz); + static addEventName(eventName: EventName, clazz: EggProtoImplClass) { + const eventNameList = MetadataUtil.initOwnArrayMetaData(EVENT_NAME, clazz, []); + eventNameList.push(eventName); } - static getEventName(clazz: EggProtoImplClass): EventName | undefined { - return MetadataUtil.getMetaData(EVENT_NAME, clazz); + static getEventNameList(clazz: EggProtoImplClass): EventName[] { + return MetadataUtil.getArrayMetaData(EVENT_NAME, clazz); } } diff --git a/core/eventbus-decorator/test/Event.test.ts b/core/eventbus-decorator/test/Event.test.ts index 2185246e..f494f8d0 100644 --- a/core/eventbus-decorator/test/Event.test.ts +++ b/core/eventbus-decorator/test/Event.test.ts @@ -6,8 +6,8 @@ import { EventInfoUtil } from '../src/EventInfoUtil'; describe('test/Event.test.ts', () => { it('should work', () => { - const eventName = EventInfoUtil.getEventName(FooHandler); - assert(eventName === 'foo'); + const eventList = EventInfoUtil.getEventNameList(FooHandler); + assert.deepStrictEqual(eventList, [ 'bar', 'foo' ]); }); it('event type check should work', async () => { diff --git a/core/eventbus-decorator/test/fixtures/right-event-handle.ts b/core/eventbus-decorator/test/fixtures/right-event-handle.ts index bc138c0b..53425f58 100644 --- a/core/eventbus-decorator/test/fixtures/right-event-handle.ts +++ b/core/eventbus-decorator/test/fixtures/right-event-handle.ts @@ -5,6 +5,7 @@ import { EventBus, Event } from '../..'; declare module '@eggjs/tegg' { interface Events { foo: (msg: string) => void; + bar: (msg: string) => void; } } @@ -19,6 +20,7 @@ export class FooProducer { } @Event('foo') +@Event('bar') export class FooHandler { handle(msg: string): void { console.log('msg: ', msg); diff --git a/core/eventbus-runtime/src/EventHandlerFactory.ts b/core/eventbus-runtime/src/EventHandlerFactory.ts index f813a286..a0c4f230 100644 --- a/core/eventbus-runtime/src/EventHandlerFactory.ts +++ b/core/eventbus-runtime/src/EventHandlerFactory.ts @@ -10,9 +10,11 @@ import { AccessLevel, SingletonProto } from '@eggjs/core-decorator'; export class EventHandlerFactory { private handlerProtoMap: Map> = new Map(); - registerHandler(event: EventName, proto: EggPrototype) { - const protos = MapUtil.getOrStore(this.handlerProtoMap, event, []); - protos.push(proto); + registerHandler(events: EventName[], proto: EggPrototype) { + for (const event of events) { + const protos = MapUtil.getOrStore(this.handlerProtoMap, event, []); + protos.push(proto); + } } hasListeners(event: EventName) { diff --git a/core/eventbus-runtime/test/EventBus.test.ts b/core/eventbus-runtime/test/EventBus.test.ts index abcd467d..0adab1e2 100644 --- a/core/eventbus-runtime/test/EventBus.test.ts +++ b/core/eventbus-runtime/test/EventBus.test.ts @@ -36,7 +36,7 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventName(HelloHandler)!, + EventInfoUtil.getEventNameList(HelloHandler), PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); @@ -66,7 +66,7 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventName(HelloHandler)!, + EventInfoUtil.getEventNameList(HelloHandler), PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); @@ -88,10 +88,10 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventName(Timeout0Handler)!, + EventInfoUtil.getEventNameList(Timeout0Handler), PrototypeUtil.getClazzProto(Timeout0Handler) as EggPrototype); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventName(Timeout100Handler)!, + EventInfoUtil.getEventNameList(Timeout100Handler), PrototypeUtil.getClazzProto(Timeout100Handler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); @@ -112,7 +112,7 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventName(HelloHandler)!, + EventInfoUtil.getEventNameList(HelloHandler), PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); @@ -144,7 +144,7 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventName(HelloHandler)!, + EventInfoUtil.getEventNameList(HelloHandler), PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); diff --git a/plugin/eventbus/lib/EventHandlerProtoManager.ts b/plugin/eventbus/lib/EventHandlerProtoManager.ts index 07e6c220..1629e470 100644 --- a/plugin/eventbus/lib/EventHandlerProtoManager.ts +++ b/plugin/eventbus/lib/EventHandlerProtoManager.ts @@ -19,8 +19,8 @@ export class EventHandlerProtoManager { async register() { const eventHandlerFactory = await this.app.getEggObject(EventHandlerFactory); for (const proto of this.protos) { - const event = proto.getMetaData(EVENT_NAME)! as EventName; - eventHandlerFactory.registerHandler(event, proto); + const eventList = proto.getMetaData(EVENT_NAME)! as EventName[]; + eventHandlerFactory.registerHandler(eventList, proto); } const eventFactory = await this.app.getEggObject(EventContextFactory); diff --git a/plugin/eventbus/test/eventbus.test.ts b/plugin/eventbus/test/eventbus.test.ts index 73046948..d56d6187 100644 --- a/plugin/eventbus/test/eventbus.test.ts +++ b/plugin/eventbus/test/eventbus.test.ts @@ -4,6 +4,7 @@ import mm, { MockApplication } from 'egg-mock'; import { TimerUtil } from '@eggjs/tegg-common-util'; import { HelloService } from './fixtures/apps/event-app/app/event-module/HelloService'; import { HelloLogger } from './fixtures/apps/event-app/app/event-module/HelloLogger'; +import { MultiEventHandler } from './fixtures/apps/event-app/app/event-module/MultiEventHandler'; describe('test/eventbus.test.ts', () => { let app: MockApplication; @@ -139,4 +140,22 @@ describe('test/eventbus.test.ts', () => { ]); assert(helloCalled === 2); }); + + it('multi event handler should work', async function() { + await app.mockModuleContextScope(async ctx => { + const helloService = await ctx.getEggObject(HelloService); + const msg: string[] = []; + mm(MultiEventHandler.prototype, 'handle', m => { + msg.push(m); + }); + const eventWaiter = await app.getEventWaiter(); + const helloEvent = eventWaiter.await('helloEgg'); + helloService.hello(); + await helloEvent; + const hiEvent = eventWaiter.await('hiEgg'); + helloService.hi(); + await hiEvent; + assert.deepStrictEqual(msg, [ '01', 'Ydream' ]); + }); + }); }); diff --git a/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/HelloService.ts b/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/HelloService.ts index ec0de28b..98addaca 100644 --- a/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/HelloService.ts +++ b/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/HelloService.ts @@ -3,6 +3,7 @@ import { AccessLevel, ContextProto, Inject, ContextEventBus } from '@eggjs/tegg' declare module '@eggjs/tegg' { interface Events { helloEgg: (msg: string) => void; + hiEgg: (msg: string) => void; trace: () => void, } } @@ -26,6 +27,10 @@ export class HelloService { this.eventBus.emit('helloEgg', '01'); } + hi() { + this.eventBus.emit('hiEgg', 'Ydream'); + } + traceTest() { this.eventBus.emit('trace'); } diff --git a/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts b/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts new file mode 100644 index 00000000..4c5536d0 --- /dev/null +++ b/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts @@ -0,0 +1,8 @@ +import { Event } from '@eggjs/tegg'; +@Event('helloEgg') +@Event('hiEgg') +export class MultiEventHandler { + handle(msg: string) { + console.log('How are you', msg); + } +} From ed6c49e778bc7745aaefd41babcf7f76bfcac8e7 Mon Sep 17 00:00:00 2001 From: Ydream <995862798@qq.com> Date: Mon, 25 Dec 2023 22:55:58 +0800 Subject: [PATCH 2/4] feat: add EggContext decorator --- core/eventbus-decorator/README.md | 15 +++++++ core/eventbus-decorator/index.ts | 1 + core/eventbus-decorator/src/EventBus.ts | 13 ++++-- core/eventbus-decorator/src/EventContext.ts | 21 ++++++++++ core/eventbus-decorator/src/EventInfoUtil.ts | 28 +++++++++++++ core/eventbus-decorator/test/Event.test.ts | 20 ++++++++-- .../fixtures/event-handle-with-context.ts | 28 +++++++++++++ .../test/fixtures/multiple-events-handle.ts | 29 ++++++++++++++ .../test/fixtures/right-event-handle.ts | 2 - .../src/EventHandlerFactory.ts | 40 ++++++++++++++----- .../eventbus-runtime/src/SingletonEventBus.ts | 22 +++++----- core/eventbus-runtime/test/EventBus.test.ts | 39 +++++++++++++++--- .../modules/event/MultiEventWithContext.ts | 36 +++++++++++++++++ plugin/eventbus/lib/EggContextEventBus.ts | 3 +- .../eventbus/lib/EventHandlerProtoManager.ts | 4 +- plugin/eventbus/test/eventbus.test.ts | 13 ++++-- .../app/event-module/MultiEventHandler.ts | 6 +-- 17 files changed, 273 insertions(+), 47 deletions(-) create mode 100644 core/eventbus-decorator/src/EventContext.ts create mode 100644 core/eventbus-decorator/test/fixtures/event-handle-with-context.ts create mode 100644 core/eventbus-decorator/test/fixtures/multiple-events-handle.ts create mode 100644 core/eventbus-runtime/test/fixtures/modules/event/MultiEventWithContext.ts diff --git a/core/eventbus-decorator/README.md b/core/eventbus-decorator/README.md index 3a199e51..5461de60 100644 --- a/core/eventbus-decorator/README.md +++ b/core/eventbus-decorator/README.md @@ -67,3 +67,18 @@ export class Foo { } } ``` + +### inject event context +inject event context if you want to know which event is being handled. +The context param must be the first param + +```ts +@Event('hello') +@Event('hi') +export class Foo { + async handle(@EventContext ctx: IEventContext, msg: string):Promise { + console.log('eventName: ', ctx.eventName); + console.log('msg: ', msg); + } +} +``` diff --git a/core/eventbus-decorator/index.ts b/core/eventbus-decorator/index.ts index 806b66d2..443390ba 100644 --- a/core/eventbus-decorator/index.ts +++ b/core/eventbus-decorator/index.ts @@ -1,6 +1,7 @@ export * from './src/EventBus'; export * from './src/Event'; export * from './src/EventInfoUtil'; +export * from './src/EventContext'; // trick for use declaration export interface Events { diff --git a/core/eventbus-decorator/src/EventBus.ts b/core/eventbus-decorator/src/EventBus.ts index abce2529..f7f31088 100644 --- a/core/eventbus-decorator/src/EventBus.ts +++ b/core/eventbus-decorator/src/EventBus.ts @@ -1,9 +1,12 @@ -import TypedEventEmitter, { Arguments } from 'typed-emitter'; +import TypedEventEmitter from 'typed-emitter'; +import type { Arguments } from 'typed-emitter'; // use @eggjs/tegg as namespace // eslint-disable-next-line import/no-unresolved import { Events } from '@eggjs/tegg'; +import { IEventContext } from './EventContext'; export type EventName = string | symbol; +export type { Arguments } from 'typed-emitter'; /** * use `emit` to emit a event @@ -38,6 +41,10 @@ export interface EventWaiter { awaitFirst(e1: E1, e2: E2, e3: E3, e4: E4): Promise<{ event: E1 | E2 | E3 | E4, args: Arguments }> } -export interface EventHandler { +type EventHandlerWithContext = { + handle: (ctx: IEventContext, ...args: Arguments) => ReturnType +}; + +export type EventHandler = { handle: Events[E]; -} +} | EventHandlerWithContext; diff --git a/core/eventbus-decorator/src/EventContext.ts b/core/eventbus-decorator/src/EventContext.ts new file mode 100644 index 00000000..4fa2a842 --- /dev/null +++ b/core/eventbus-decorator/src/EventContext.ts @@ -0,0 +1,21 @@ +// use @eggjs/tegg as namespace +// eslint-disable-next-line import/no-unresolved +import { Events } from '@eggjs/tegg'; +import { EggProtoImplClass } from '@eggjs/core-decorator'; +import assert from 'assert'; +import { EventInfoUtil } from './EventInfoUtil'; + +export interface IEventContext{ + eventName: keyof Events +} + +export function EventContext() { + return function(target: any, propertyKey: PropertyKey, parameterIndex: number) { + assert(propertyKey === 'handle', + `[eventHandler ${target.name}] expect method name be handle, but now is ${String(propertyKey)}`); + assert(parameterIndex === 0, + `[eventHandler ${target.name}] expect param EventContext be the first param`); + const clazz = target.constructor as EggProtoImplClass; + EventInfoUtil.setEventHandlerContextInject(true, clazz); + }; +} diff --git a/core/eventbus-decorator/src/EventInfoUtil.ts b/core/eventbus-decorator/src/EventInfoUtil.ts index bbc32c3e..f0350572 100644 --- a/core/eventbus-decorator/src/EventInfoUtil.ts +++ b/core/eventbus-decorator/src/EventInfoUtil.ts @@ -2,8 +2,16 @@ import { EggProtoImplClass, MetadataUtil } from '@eggjs/core-decorator'; import { EventName } from './EventBus'; export const EVENT_NAME = Symbol.for('EggPrototype#eventName'); +export const EVENT_CONTEXT_INJECT = Symbol.for('EggPrototype#event#handler#context#inject'); export class EventInfoUtil { + /** + * @deprecated + */ + static setEventName(eventName: EventName, clazz: EggProtoImplClass) { + EventInfoUtil.addEventName(eventName, clazz); + } + static addEventName(eventName: EventName, clazz: EggProtoImplClass) { const eventNameList = MetadataUtil.initOwnArrayMetaData(EVENT_NAME, clazz, []); eventNameList.push(eventName); @@ -12,4 +20,24 @@ export class EventInfoUtil { static getEventNameList(clazz: EggProtoImplClass): EventName[] { return MetadataUtil.getArrayMetaData(EVENT_NAME, clazz); } + + /** + * @deprecated + * return the last eventName which is subscribed firstly by Event decorator + */ + static getEventName(clazz: EggProtoImplClass): EventName | undefined { + const eventNameList = MetadataUtil.initOwnArrayMetaData(EVENT_NAME, clazz, []); + if (eventNameList.length !== 0) { + return eventNameList[eventNameList.length - 1]; + } + return undefined; + } + + static setEventHandlerContextInject(enable: boolean, clazz: EggProtoImplClass): void { + MetadataUtil.defineMetaData(EVENT_CONTEXT_INJECT, enable, clazz); + } + + static getEventHandlerContextInject(clazz: EggProtoImplClass): boolean { + return MetadataUtil.getMetaData(EVENT_CONTEXT_INJECT, clazz) ?? false; + } } diff --git a/core/eventbus-decorator/test/Event.test.ts b/core/eventbus-decorator/test/Event.test.ts index f494f8d0..d907715d 100644 --- a/core/eventbus-decorator/test/Event.test.ts +++ b/core/eventbus-decorator/test/Event.test.ts @@ -2,12 +2,26 @@ import assert from 'assert'; import path from 'path'; import coffee from 'coffee'; import { FooHandler } from './fixtures/right-event-handle'; +import { MultiHandler } from './fixtures/multiple-events-handle'; +import { EventContextHandler } from './fixtures/event-handle-with-context'; import { EventInfoUtil } from '../src/EventInfoUtil'; describe('test/Event.test.ts', () => { - it('should work', () => { - const eventList = EventInfoUtil.getEventNameList(FooHandler); - assert.deepStrictEqual(eventList, [ 'bar', 'foo' ]); + it('getEventName should work', () => { + const event = EventInfoUtil.getEventName(FooHandler); + assert.equal(event, 'foo'); + }); + + it('getEventNameList should work', function() { + const event = EventInfoUtil.getEventName(MultiHandler); + assert.deepStrictEqual(event, 'hello'); + const eventList = EventInfoUtil.getEventNameList(MultiHandler); + assert.deepStrictEqual(eventList, [ 'hi', 'hello' ]); + }); + + it('getEventHandlerContextInject', function() { + assert.equal(EventInfoUtil.getEventHandlerContextInject(EventContextHandler), true); + assert.equal(EventInfoUtil.getEventHandlerContextInject(FooHandler), false); }); it('event type check should work', async () => { diff --git a/core/eventbus-decorator/test/fixtures/event-handle-with-context.ts b/core/eventbus-decorator/test/fixtures/event-handle-with-context.ts new file mode 100644 index 00000000..414951c7 --- /dev/null +++ b/core/eventbus-decorator/test/fixtures/event-handle-with-context.ts @@ -0,0 +1,28 @@ +import { Inject, SingletonProto } from '@eggjs/core-decorator'; +import {EventBus, Event, IEventContext, EventContext} from '../..'; + + +declare module '@eggjs/tegg' { + interface Events { + ctxEvent: (msg: string) => void; + } +} + +@SingletonProto() +export class EventContextProducer { + @Inject() + private readonly eventBus: EventBus; + + trigger() { + this.eventBus.emit('ctxEvent', 'hello'); + } +} + +@Event('ctxEvent') +export class EventContextHandler { + handle(@EventContext() ctx: IEventContext, msg: string): void { + console.log('ctx: ', ctx); + console.log('msg: ', msg); + } +} + diff --git a/core/eventbus-decorator/test/fixtures/multiple-events-handle.ts b/core/eventbus-decorator/test/fixtures/multiple-events-handle.ts new file mode 100644 index 00000000..4241c64c --- /dev/null +++ b/core/eventbus-decorator/test/fixtures/multiple-events-handle.ts @@ -0,0 +1,29 @@ +import { Inject, SingletonProto } from '@eggjs/core-decorator'; +import { EventBus, Event } from '../..'; + + +declare module '@eggjs/tegg' { + interface Events { + hello: (msg: string) => void; + hi: (msg: string) => void; + } +} + +@SingletonProto() +export class MultiProducer { + @Inject() + private readonly eventBus: EventBus; + + trigger() { + this.eventBus.emit('hello', 'Ydream'); + } +} + +@Event('hello') +@Event('hi') +export class MultiHandler { + handle(msg: string): void { + console.log('msg: ', msg); + } +} + diff --git a/core/eventbus-decorator/test/fixtures/right-event-handle.ts b/core/eventbus-decorator/test/fixtures/right-event-handle.ts index 53425f58..bc138c0b 100644 --- a/core/eventbus-decorator/test/fixtures/right-event-handle.ts +++ b/core/eventbus-decorator/test/fixtures/right-event-handle.ts @@ -5,7 +5,6 @@ import { EventBus, Event } from '../..'; declare module '@eggjs/tegg' { interface Events { foo: (msg: string) => void; - bar: (msg: string) => void; } } @@ -20,7 +19,6 @@ export class FooProducer { } @Event('foo') -@Event('bar') export class FooHandler { handle(msg: string): void { console.log('msg: ', msg); diff --git a/core/eventbus-runtime/src/EventHandlerFactory.ts b/core/eventbus-runtime/src/EventHandlerFactory.ts index a0c4f230..c571eefe 100644 --- a/core/eventbus-runtime/src/EventHandlerFactory.ts +++ b/core/eventbus-runtime/src/EventHandlerFactory.ts @@ -1,4 +1,4 @@ -import { EventHandler, EventName, Events } from '@eggjs/eventbus-decorator'; +import { EventHandler, EventName, Events, Arguments, EVENT_CONTEXT_INJECT } from '@eggjs/eventbus-decorator'; import { EggContainerFactory } from '@eggjs/tegg-runtime'; import { EggPrototype } from '@eggjs/tegg-metadata'; import { MapUtil } from '@eggjs/tegg-common-util'; @@ -10,22 +10,42 @@ import { AccessLevel, SingletonProto } from '@eggjs/core-decorator'; export class EventHandlerFactory { private handlerProtoMap: Map> = new Map(); - registerHandler(events: EventName[], proto: EggPrototype) { - for (const event of events) { - const protos = MapUtil.getOrStore(this.handlerProtoMap, event, []); - protos.push(proto); - } + registerHandler(event: EventName, proto: EggPrototype) { + const protos = MapUtil.getOrStore(this.handlerProtoMap, event, []); + protos.push(proto); } hasListeners(event: EventName) { return this.handlerProtoMap.has(event); } - async getHandlers(event: EventName): Promise>> { + getHandlerProtos(event: EventName): Array { const handlerProtos = this.handlerProtoMap.get(event) || []; - const eggObjs = await Promise.all(handlerProtos.map(proto => { - return EggContainerFactory.getOrCreateEggObject(proto, proto.name); + return handlerProtos; + } + + async getHandler(proto: EggPrototype): Promise> { + const eggObj = await EggContainerFactory.getOrCreateEggObject(proto, proto.name); + return eggObj.obj as EventHandler; + } + + async getHandlers(event: EventName): Promise>> { + const handlerProtos = this.getHandlerProtos(event); + return await Promise.all(handlerProtos.map(proto => { + return this.getHandler(proto); })); - return eggObjs.map(t => t.obj as EventHandler); + } + + async handle(eventName: EventName, proto: EggPrototype, args: Arguments): Promise { + const handler = await this.getHandler(proto); + const enableInjectCtx = proto.getMetaData(EVENT_CONTEXT_INJECT) ?? false; + if (enableInjectCtx) { + const ctx = { + eventName, + }; + await Reflect.apply(handler.handle, handler, [ ctx, ...args ]); + } else { + await Reflect.apply(handler.handle, handler, args); + } } } diff --git a/core/eventbus-runtime/src/SingletonEventBus.ts b/core/eventbus-runtime/src/SingletonEventBus.ts index b95c24ea..57e0509e 100644 --- a/core/eventbus-runtime/src/SingletonEventBus.ts +++ b/core/eventbus-runtime/src/SingletonEventBus.ts @@ -1,5 +1,6 @@ import { AccessLevel, Inject, SingletonProto } from '@eggjs/core-decorator'; import { EventBus, Events, EventWaiter, EventName, CORK_ID } from '@eggjs/eventbus-decorator'; +import type { Arguments } from '@eggjs/eventbus-decorator'; import { ContextHandler, EggContext } from '@eggjs/tegg-runtime'; import type { EggLogger } from 'egg'; import { EventContextFactory } from './EventContextFactory'; @@ -8,11 +9,6 @@ import { EventEmitter } from 'events'; import awaitEvent from 'await-event'; import awaitFirst from 'await-first'; -// from typed-emitter -type Array = [ T ] extends [ (...args: infer U) => any ] - ? U - : [ T ] extends [ void ] ? [] : [ T ]; - export interface Event { name: EventName; args: Array; @@ -56,15 +52,15 @@ export class SingletonEventBus implements EventBus, EventWaiter { return this; } - async await(event: E): Promise> { + async await(event: E): Promise> { return awaitEvent(this.emitter, event); } - awaitFirst(...e: Array): Promise<{ event: EventName, args: Array }> { + awaitFirst(...e: Array): Promise<{ event: EventName, args: Arguments }> { return awaitFirst(this.emitter, e); } - emit(event: E, ...args: Array): boolean { + emit(event: E, ...args: Arguments): boolean { const ctx = this.eventContextFactory.createContext(); const hasListener = this.eventHandlerFactory.hasListeners(event); this.doEmit(ctx, event, args); @@ -112,7 +108,7 @@ export class SingletonEventBus implements EventBus, EventWaiter { corkdEvents.events.push(event); } - emitWithContext(parentContext: EggContext, event: E, args: Array): boolean { + emitWithContext(parentContext: EggContext, event: E, args: Arguments): boolean { const corkId = parentContext.get(CORK_ID); const hasListener = this.eventHandlerFactory.hasListeners(event); if (corkId) { @@ -145,13 +141,13 @@ export class SingletonEventBus implements EventBus, EventWaiter { await ctx.init(lifecycle); } try { - const handlers = await this.eventHandlerFactory.getHandlers(event); - await Promise.all(handlers.map(async handler => { + const handlerProtos = this.eventHandlerFactory.getHandlerProtos(event); + await Promise.all(handlerProtos.map(async proto => { try { - await Reflect.apply(handler.handle, handler, args); + await this.eventHandlerFactory.handle(event, proto, args); } catch (e) { // should wait all handlers done then destroy ctx - e.message = `[EventBus] process event ${String(event)} failed: ${e.message}`; + e.message = `[EventBus] process event ${String(event)} for handler ${String(proto.name)} failed: ${e.message}`; this.logger.error(e); } })); diff --git a/core/eventbus-runtime/test/EventBus.test.ts b/core/eventbus-runtime/test/EventBus.test.ts index 0adab1e2..215bc9db 100644 --- a/core/eventbus-runtime/test/EventBus.test.ts +++ b/core/eventbus-runtime/test/EventBus.test.ts @@ -10,6 +10,7 @@ import { EventInfoUtil, CORK_ID } from '@eggjs/eventbus-decorator'; import { CoreTestHelper, EggTestContext } from '@eggjs/module-test-util'; import { EventContextFactory, EventHandlerFactory, SingletonEventBus } from '..'; import { Timeout0Handler, Timeout100Handler, TimeoutProducer } from './fixtures/modules/event/MultiEvent'; +import { MultiWithContextHandler, MultiWithContextProducer } from './fixtures/modules/event/MultiEventWithContext'; describe('test/EventBus.test.ts', () => { let modules: Array; @@ -36,7 +37,7 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventNameList(HelloHandler), + EventInfoUtil.getEventName(HelloHandler)!, PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); @@ -54,6 +55,32 @@ describe('test/EventBus.test.ts', () => { }); }); + it('should work with EventContext', async function() { + await EggTestContext.mockContext(async (ctx: EggTestContext) => { + const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory); + eventContextFactory.registerContextCreator(() => { + return ctx; + }); + const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); + EventInfoUtil.getEventNameList(MultiWithContextHandler) + .forEach(eventName => + eventHandlerFactory.registerHandler(eventName, PrototypeUtil.getClazzProto(MultiWithContextHandler) as EggPrototype)); + + const eventBus = await CoreTestHelper.getObject(SingletonEventBus); + const producer = await CoreTestHelper.getObject(MultiWithContextProducer); + const fooEvent = eventBus.await('foo'); + producer.foo(); + await fooEvent; + assert.equal(MultiWithContextHandler.eventName, 'foo'); + assert.equal(MultiWithContextHandler.msg, '123'); + const barEvent = eventBus.await('bar'); + producer.bar(); + await barEvent; + assert.equal(MultiWithContextHandler.eventName, 'bar'); + assert.equal(MultiWithContextHandler.msg, '321'); + }); + }); + it('destroy should be called', async () => { await EggTestContext.mockContext(async (ctx: EggTestContext) => { let destroyCalled = false; @@ -66,7 +93,7 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventNameList(HelloHandler), + EventInfoUtil.getEventName(HelloHandler)!, PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); @@ -88,10 +115,10 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventNameList(Timeout0Handler), + EventInfoUtil.getEventName(Timeout0Handler)!, PrototypeUtil.getClazzProto(Timeout0Handler) as EggPrototype); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventNameList(Timeout100Handler), + EventInfoUtil.getEventName(Timeout100Handler)!, PrototypeUtil.getClazzProto(Timeout100Handler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); @@ -112,7 +139,7 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventNameList(HelloHandler), + EventInfoUtil.getEventName(HelloHandler)!, PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); @@ -144,7 +171,7 @@ describe('test/EventBus.test.ts', () => { }); const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); eventHandlerFactory.registerHandler( - EventInfoUtil.getEventNameList(HelloHandler), + EventInfoUtil.getEventName(HelloHandler)!, PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype); const eventBus = await CoreTestHelper.getObject(SingletonEventBus); diff --git a/core/eventbus-runtime/test/fixtures/modules/event/MultiEventWithContext.ts b/core/eventbus-runtime/test/fixtures/modules/event/MultiEventWithContext.ts new file mode 100644 index 00000000..d4f4b9d2 --- /dev/null +++ b/core/eventbus-runtime/test/fixtures/modules/event/MultiEventWithContext.ts @@ -0,0 +1,36 @@ +import {Event, EventBus, EventContext, IEventContext} from '@eggjs/eventbus-decorator'; +import { AccessLevel, Inject, SingletonProto } from '@eggjs/core-decorator'; + +declare module '@eggjs/eventbus-decorator' { + interface Events { + foo: (msg: string) => void; + bar: (msg: string) => void; + } +} + +@SingletonProto({ + accessLevel: AccessLevel.PUBLIC, +}) +export class MultiWithContextProducer { + @Inject() + private readonly eventBus: EventBus; + + foo() { + this.eventBus.emit('foo', '123'); + } + + bar() { + this.eventBus.emit('bar', '321'); + } +} + +@Event('foo') +@Event('bar') +export class MultiWithContextHandler { + static eventName: string; + static msg: string; + async handle(@EventContext() ctx: IEventContext, msg: string) { + MultiWithContextHandler.eventName = ctx.eventName; + MultiWithContextHandler.msg = msg + } +} diff --git a/plugin/eventbus/lib/EggContextEventBus.ts b/plugin/eventbus/lib/EggContextEventBus.ts index 4282ad99..f6cd1407 100644 --- a/plugin/eventbus/lib/EggContextEventBus.ts +++ b/plugin/eventbus/lib/EggContextEventBus.ts @@ -4,6 +4,7 @@ import { Events, PrototypeUtil, CORK_ID, ContextEventBus } from '@eggjs/tegg'; import { SingletonEventBus } from '@eggjs/tegg-eventbus-runtime'; import { EggPrototype } from '@eggjs/tegg-metadata'; import { ContextHandler, EggContext } from '@eggjs/tegg-runtime'; +import type { Arguments } from '@eggjs/tegg'; export class EggContextEventBus implements ContextEventBus { private readonly eventBus: SingletonEventBus; @@ -33,7 +34,7 @@ export class EggContextEventBus implements ContextEventBus { } } - emit(event: E, ...args: any): boolean { + emit(event: E, ...args: Arguments): boolean { return this.eventBus.emitWithContext(this.context, event, args); } diff --git a/plugin/eventbus/lib/EventHandlerProtoManager.ts b/plugin/eventbus/lib/EventHandlerProtoManager.ts index 1629e470..af523b54 100644 --- a/plugin/eventbus/lib/EventHandlerProtoManager.ts +++ b/plugin/eventbus/lib/EventHandlerProtoManager.ts @@ -19,8 +19,8 @@ export class EventHandlerProtoManager { async register() { const eventHandlerFactory = await this.app.getEggObject(EventHandlerFactory); for (const proto of this.protos) { - const eventList = proto.getMetaData(EVENT_NAME)! as EventName[]; - eventHandlerFactory.registerHandler(eventList, proto); + const eventList = proto.getMetaData(EVENT_NAME) as EventName[] ?? []; + eventList.forEach(event => eventHandlerFactory.registerHandler(event, proto)); } const eventFactory = await this.app.getEggObject(EventContextFactory); diff --git a/plugin/eventbus/test/eventbus.test.ts b/plugin/eventbus/test/eventbus.test.ts index d56d6187..07228bc2 100644 --- a/plugin/eventbus/test/eventbus.test.ts +++ b/plugin/eventbus/test/eventbus.test.ts @@ -144,18 +144,23 @@ describe('test/eventbus.test.ts', () => { it('multi event handler should work', async function() { await app.mockModuleContextScope(async ctx => { const helloService = await ctx.getEggObject(HelloService); - const msg: string[] = []; - mm(MultiEventHandler.prototype, 'handle', m => { - msg.push(m); + let eventName = ''; + let msg = ''; + mm(MultiEventHandler.prototype, 'handle', (ctx, m) => { + eventName = ctx.eventName; + msg = m; }); const eventWaiter = await app.getEventWaiter(); const helloEvent = eventWaiter.await('helloEgg'); helloService.hello(); await helloEvent; + assert.equal(eventName, 'helloEgg'); + assert.equal(msg, '01'); const hiEvent = eventWaiter.await('hiEgg'); helloService.hi(); await hiEvent; - assert.deepStrictEqual(msg, [ '01', 'Ydream' ]); + assert.equal(eventName, 'hiEgg'); + assert.equal(msg, 'Ydream'); }); }); }); diff --git a/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts b/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts index 4c5536d0..a857a436 100644 --- a/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts +++ b/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts @@ -1,8 +1,8 @@ -import { Event } from '@eggjs/tegg'; +import { Event, EventContext, IEventContext } from '@eggjs/tegg'; @Event('helloEgg') @Event('hiEgg') export class MultiEventHandler { - handle(msg: string) { - console.log('How are you', msg); + handle(@EventContext()ctx: IEventContext, msg: string) { + console.log('How are you', msg, ctx); } } From e499e861f9cb5c52c0a4d83694fd286a39e4b4a6 Mon Sep 17 00:00:00 2001 From: Ydream <995862798@qq.com> Date: Tue, 26 Dec 2023 14:01:35 +0800 Subject: [PATCH 3/4] feat: add some test use case --- core/eventbus-decorator/README.md | 2 +- core/eventbus-decorator/test/Event.test.ts | 12 +++++- .../test/fixtures/empty-handle.ts | 1 + core/eventbus-runtime/test/EventBus.test.ts | 41 +++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 core/eventbus-decorator/test/fixtures/empty-handle.ts diff --git a/core/eventbus-decorator/README.md b/core/eventbus-decorator/README.md index 5461de60..b749c1b2 100644 --- a/core/eventbus-decorator/README.md +++ b/core/eventbus-decorator/README.md @@ -76,7 +76,7 @@ The context param must be the first param @Event('hello') @Event('hi') export class Foo { - async handle(@EventContext ctx: IEventContext, msg: string):Promise { + async handle(@EventContext() ctx: IEventContext, msg: string):Promise { console.log('eventName: ', ctx.eventName); console.log('msg: ', msg); } diff --git a/core/eventbus-decorator/test/Event.test.ts b/core/eventbus-decorator/test/Event.test.ts index d907715d..f6b69f16 100644 --- a/core/eventbus-decorator/test/Event.test.ts +++ b/core/eventbus-decorator/test/Event.test.ts @@ -4,12 +4,13 @@ import coffee from 'coffee'; import { FooHandler } from './fixtures/right-event-handle'; import { MultiHandler } from './fixtures/multiple-events-handle'; import { EventContextHandler } from './fixtures/event-handle-with-context'; +import { EmptyHandler } from './fixtures/empty-handle'; import { EventInfoUtil } from '../src/EventInfoUtil'; describe('test/Event.test.ts', () => { it('getEventName should work', () => { - const event = EventInfoUtil.getEventName(FooHandler); - assert.equal(event, 'foo'); + assert.equal(EventInfoUtil.getEventName(FooHandler), 'foo'); + assert.equal(EventInfoUtil.getEventName(EmptyHandler), undefined); }); it('getEventNameList should work', function() { @@ -19,6 +20,13 @@ describe('test/Event.test.ts', () => { assert.deepStrictEqual(eventList, [ 'hi', 'hello' ]); }); + it('setEventName should work', function() { + EventInfoUtil.setEventName('foo', EmptyHandler); + assert.equal(EventInfoUtil.getEventName(EmptyHandler), 'foo'); + EventInfoUtil.setEventName('bar', EmptyHandler); + assert.equal(EventInfoUtil.getEventName(EmptyHandler), 'bar'); + }); + it('getEventHandlerContextInject', function() { assert.equal(EventInfoUtil.getEventHandlerContextInject(EventContextHandler), true); assert.equal(EventInfoUtil.getEventHandlerContextInject(FooHandler), false); diff --git a/core/eventbus-decorator/test/fixtures/empty-handle.ts b/core/eventbus-decorator/test/fixtures/empty-handle.ts new file mode 100644 index 00000000..6dc3d14e --- /dev/null +++ b/core/eventbus-decorator/test/fixtures/empty-handle.ts @@ -0,0 +1 @@ +export class EmptyHandler{} diff --git a/core/eventbus-runtime/test/EventBus.test.ts b/core/eventbus-runtime/test/EventBus.test.ts index 215bc9db..a82a27be 100644 --- a/core/eventbus-runtime/test/EventBus.test.ts +++ b/core/eventbus-runtime/test/EventBus.test.ts @@ -81,6 +81,47 @@ describe('test/EventBus.test.ts', () => { }); }); + it('EventBus.awaitFirst should work', async function() { + await EggTestContext.mockContext(async (ctx: EggTestContext) => { + const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory); + eventContextFactory.registerContextCreator(() => { + return ctx; + }); + const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); + EventInfoUtil.getEventNameList(MultiWithContextHandler) + .forEach(eventName => + eventHandlerFactory.registerHandler(eventName, PrototypeUtil.getClazzProto(MultiWithContextHandler) as EggPrototype)); + + const eventBus = await CoreTestHelper.getObject(SingletonEventBus); + const producer = await CoreTestHelper.getObject(MultiWithContextProducer); + const fooEvent = eventBus.awaitFirst('foo', 'bar'); + producer.foo(); + await fooEvent; + assert.equal(MultiWithContextHandler.eventName, 'foo'); + assert.equal(MultiWithContextHandler.msg, '123'); + }); + }); + + it('EventHandlerFactory.getHandlers should work', async function() { + await EggTestContext.mockContext(async (ctx: EggTestContext) => { + const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory); + eventContextFactory.registerContextCreator(() => { + return ctx; + }); + const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory); + EventInfoUtil.getEventNameList(MultiWithContextHandler) + .forEach(eventName => + eventHandlerFactory.registerHandler(eventName, PrototypeUtil.getClazzProto(MultiWithContextHandler) as EggPrototype)); + const handlers = await eventHandlerFactory.getHandlers('foo'); + assert.equal(handlers.length, 1); + const handler = handlers[0]; + assert(handler instanceof MultiWithContextHandler); + await Reflect.apply(handler.handle, handler, [{ eventName: 'foo' }, '123' ]); + assert.equal(MultiWithContextHandler.eventName, 'foo'); + assert.equal(MultiWithContextHandler.msg, '123'); + }); + }); + it('destroy should be called', async () => { await EggTestContext.mockContext(async (ctx: EggTestContext) => { let destroyCalled = false; From 598524eac518fd53231ddca7c760aa027ae76310 Mon Sep 17 00:00:00 2001 From: Ydream <995862798@qq.com> Date: Tue, 26 Dec 2023 14:37:30 +0800 Subject: [PATCH 4/4] chore: import type --- core/eventbus-decorator/src/Event.ts | 2 +- core/eventbus-decorator/src/EventBus.ts | 2 +- core/eventbus-decorator/src/EventContext.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/eventbus-decorator/src/Event.ts b/core/eventbus-decorator/src/Event.ts index 2e3096be..40d8718a 100644 --- a/core/eventbus-decorator/src/Event.ts +++ b/core/eventbus-decorator/src/Event.ts @@ -4,7 +4,7 @@ import { EventHandler } from '../index'; import { EventInfoUtil } from './EventInfoUtil'; // use @eggjs/tegg as namespace // eslint-disable-next-line import/no-unresolved -import { Events } from '@eggjs/tegg'; +import type { Events } from '@eggjs/tegg'; export function Event(eventName: E) { return function(clazz: new () => EventHandler) { diff --git a/core/eventbus-decorator/src/EventBus.ts b/core/eventbus-decorator/src/EventBus.ts index f7f31088..82b02328 100644 --- a/core/eventbus-decorator/src/EventBus.ts +++ b/core/eventbus-decorator/src/EventBus.ts @@ -2,7 +2,7 @@ import TypedEventEmitter from 'typed-emitter'; import type { Arguments } from 'typed-emitter'; // use @eggjs/tegg as namespace // eslint-disable-next-line import/no-unresolved -import { Events } from '@eggjs/tegg'; +import type { Events } from '@eggjs/tegg'; import { IEventContext } from './EventContext'; export type EventName = string | symbol; diff --git a/core/eventbus-decorator/src/EventContext.ts b/core/eventbus-decorator/src/EventContext.ts index 4fa2a842..23d0111a 100644 --- a/core/eventbus-decorator/src/EventContext.ts +++ b/core/eventbus-decorator/src/EventContext.ts @@ -1,6 +1,6 @@ // use @eggjs/tegg as namespace // eslint-disable-next-line import/no-unresolved -import { Events } from '@eggjs/tegg'; +import type { Events } from '@eggjs/tegg'; import { EggProtoImplClass } from '@eggjs/core-decorator'; import assert from 'assert'; import { EventInfoUtil } from './EventInfoUtil';