Skip to content

Commit

Permalink
feat: add event handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
nichenqin committed Oct 18, 2024
1 parent 5ba1926 commit 038f7a9
Show file tree
Hide file tree
Showing 29 changed files with 437 additions and 14 deletions.
7 changes: 4 additions & 3 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"build:docker": "bun build --compile src/index.ts --target=bun --packages=external --sourcemap --outfile undb"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.673.0",
"@aws-sdk/s3-request-presigner": "^3.673.0",
"@aws-sdk/client-s3": "^3.674.0",
"@aws-sdk/s3-request-presigner": "^3.674.0",
"@elysiajs/cors": "1.1.0",
"@elysiajs/cron": "1.1.0",
"@elysiajs/html": "1.1.0",
Expand All @@ -25,6 +25,7 @@
"@undb/authz": "workspace:*",
"@undb/base": "workspace:*",
"@undb/command-handlers": "workspace:*",
"@undb/event-handlers": "workspace:*",
"@undb/context": "workspace:*",
"@undb/cqrs": "workspace:*",
"@undb/dashboard": "workspace:*",
Expand All @@ -41,7 +42,7 @@
"@undb/trpc": "workspace:*",
"@undb/webhook": "workspace:*",
"arctic": "^1.9.2",
"bun": "^1.1.30",
"bun": "^1.1.31",
"core-js": "^3.38.1",
"elysia": "1.1.7",
"got": "^14.4.3",
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/registry/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { registerCommands } from "@undb/command-handlers"
import { registerQueries } from "@undb/query-handlers"

import { registerEvents } from "@undb/event-handlers"
import { registerWebhook } from "../modules"
import { registerStorage } from "../modules/file/storage"
import { registerMail } from "../modules/mail/mail.register"
Expand All @@ -13,6 +14,7 @@ export const register = () => {
registerContext()
registerDb()
registerCommands()
registerEvents()
registerQueries()
registerWebhook()
}
4 changes: 2 additions & 2 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"@types/lodash.unzip": "^3.4.9",
"@types/papaparse": "^5.3.14",
"@types/sortablejs": "latest",
"@typescript-eslint/eslint-plugin": "^8.9.0",
"@typescript-eslint/parser": "^8.9.0",
"@typescript-eslint/eslint-plugin": "^8.10.0",
"@typescript-eslint/parser": "^8.10.0",
"@undb/commands": "workspace:*",
"@undb/domain": "workspace:*",
"@undb/i18n": "workspace:*",
Expand Down
Binary file modified bun.lockb
Binary file not shown.
3 changes: 3 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[install]
# set default registry as a string
registry = "https://registry.npmmirror.com"
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"drizzle-kit": "^0.26.2",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"npm-run-all2": "^6.2.3",
"npm-run-all2": "^6.2.4",
"prettier": "^3.3.3",
"turbo": "^2.1.3"
},
Expand All @@ -33,7 +33,7 @@
"engines": {
"node": ">=18"
},
"packageManager": "bun@1.1.30",
"packageManager": "bun@1.1.31",
"workspaces": [
"apps/*",
"packages/*"
Expand Down
3 changes: 3 additions & 0 deletions packages/cqrs/src/decorators/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ export const COMMAND_HANDLER_METADATA = Symbol.for("COMMAND_HANDLER")

export const QUERY_METADATA = Symbol.for("QUERY")
export const QUERY_HANDLER_METADATA = Symbol.for("QUERY_HANDLER")

export const EVENT_METADATA = Symbol.for("EVENT")
export const EVENT_HANDLER_METADATA = Symbol.for("EVENT_HANDLER")
15 changes: 15 additions & 0 deletions packages/cqrs/src/decorators/event-handler.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import "reflect-metadata"

import type { BaseEvent } from "@undb/domain"
import type { Class } from "type-fest"
import { v4 } from "uuid"
import { EVENT_HANDLER_METADATA, EVENT_METADATA } from "./constants"

export const eventHandler = (event: Class<BaseEvent>): ClassDecorator => {
return (target: object) => {
if (!Reflect.hasOwnMetadata(EVENT_METADATA, event)) {
Reflect.defineMetadata(EVENT_METADATA, { id: v4() }, event)
}
Reflect.defineMetadata(EVENT_HANDLER_METADATA, event, target)
}
}
1 change: 1 addition & 0 deletions packages/cqrs/src/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./command-handler.decorator"
export * from "./event-handler.decorator"
export * from "./query-handler.decorator"
14 changes: 14 additions & 0 deletions packages/cqrs/src/default-event-publisher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { BaseEvent, IEventPublisher } from "@undb/domain"
import { Subject } from "rxjs"

export class DefaultEventPubSub<E extends BaseEvent> implements IEventPublisher<E> {
constructor(public subject$: Subject<E>) {}

async publish(event: E): Promise<void> {
this.subject$.next(event)
}

async publishMany(events: E[]): Promise<void> {
events.forEach((event) => this.subject$.next(event))
}
}
74 changes: 74 additions & 0 deletions packages/cqrs/src/event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { container, singleton } from "@undb/di"
import type { BaseEvent, EventMetadata, IEventBus, IEventHandler, IEventPublisher } from "@undb/domain"
import { Subject } from "rxjs"
import type { Class } from "type-fest"
import { EVENT_HANDLER_METADATA, EVENT_METADATA } from "./decorators/constants"
import { DefaultEventPubSub } from "./default-event-publisher"
import { EventHandlerNotFoundException } from "./exceptions/event-handler-not-found.exception"
import { InvalidEventHandlerException } from "./exceptions/invalid-event-handler.exception"

export type EventHandlerType = Class<IEventHandler<BaseEvent, any>>

@singleton()
export class EventBus<TEvent extends BaseEvent = BaseEvent> implements IEventBus<TEvent> {
private subject = new Subject<TEvent>()
private readonly publisher: IEventPublisher = new DefaultEventPubSub(this.subject)

#handlers = new Map<string, IEventHandler<TEvent, any>>()

async publish(event: TEvent): Promise<void> {
console.log("publish event", event)
const eventId = this.getEventId(event)
const handler = this.#handlers.get(eventId)
if (!handler) {
const eventName = this.getEventName(event)
throw new EventHandlerNotFoundException(eventName)
}
this.publisher.publish(event)
return handler.handle(event)
}

async publishMany(events: TEvent[]): Promise<void> {
await Promise.all(events.map((event) => this.publish(event)))
}

register(handlers: EventHandlerType[]) {
handlers.forEach((handler) => this.registerHandler(handler))
}

private bind<T extends TEvent>(handler: IEventHandler<T, any>, id: string) {
this.#handlers.set(id, handler)
}

protected registerHandler(handler: EventHandlerType) {
const instance = container.resolve(handler)
if (!instance) {
return
}
const target = this.reflectEventId(handler)
if (!target) {
throw new InvalidEventHandlerException()
}
this.bind(instance as IEventHandler<TEvent, any>, target)
}

private reflectEventId(handler: EventHandlerType): string | undefined {
const event: BaseEvent = Reflect.getMetadata(EVENT_HANDLER_METADATA, handler)
const eventMetadata: EventMetadata = Reflect.getMetadata(EVENT_METADATA, event)
return eventMetadata.id
}

private getEventId(event: TEvent): string {
const { constructor: eventType } = Object.getPrototypeOf(event)
const eventMetadata: EventMetadata = Reflect.getMetadata(EVENT_METADATA, eventType)
if (!eventMetadata) {
throw new EventHandlerNotFoundException(eventType.name)
}
return eventMetadata.id
}

private getEventName(event: TEvent): string {
const { constructor } = Object.getPrototypeOf(event)
return constructor.name as string
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class EventHandlerNotFoundException extends Error {
constructor(eventId: string) {
super(`The event handler for the "${eventId}" event was not found!`)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class InvalidEventHandlerException extends Error {
constructor() {
super(`Invalid event handler exception (missing @eventHandler() decorator?)`)
}
}
1 change: 1 addition & 0 deletions packages/cqrs/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./command-bus"
export * from "./decorators"
export * from "./event-bus"
export * from "./query-bus"
6 changes: 6 additions & 0 deletions packages/domain/src/event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { BaseEvent } from "./event.js"

export interface IEventBus<TEvent extends BaseEvent = BaseEvent> {
publish(event: TEvent): Promise<void>
publishMany(events: TEvent[]): Promise<void>
}
3 changes: 3 additions & 0 deletions packages/domain/src/event-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface EventMetadata {
id: string
}
6 changes: 6 additions & 0 deletions packages/domain/src/event-publisher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { BaseEvent } from "./event"

export interface IEventPublisher<TEvent extends BaseEvent = BaseEvent> {
publish(event: TEvent): Promise<void>
publishMany(events: TEvent[]): Promise<void>
}
3 changes: 3 additions & 0 deletions packages/domain/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ export * from "./command.js"
export * from "./date.vo.js"
export * from "./email.vo.js"
export * from "./entity.base.js"
export * from "./event-bus.js"
export * from "./event-handler.js"
export * from "./event-metadata.js"
export * from "./event-publisher.js"
export * from "./event.js"
export * from "./exception.base.js"
export * from "./exceptions"
Expand Down
Loading

0 comments on commit 038f7a9

Please sign in to comment.