diff --git a/packages/backend/server/src/app.ts b/packages/backend/server/src/app.ts index cab18af060..afc5ab7b1a 100644 --- a/packages/backend/server/src/app.ts +++ b/packages/backend/server/src/app.ts @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { CacheModule } from './cache'; import { ConfigModule } from './config'; +import { EventModule } from './event'; import { BusinessModules } from './modules'; import { AuthModule } from './modules/auth'; import { PrismaModule } from './prisma'; @@ -14,6 +15,7 @@ const BasicModules = [ PrismaModule, ConfigModule.forRoot(), CacheModule, + EventModule, StorageModule.forRoot(), SessionModule, RateLimiterModule, diff --git a/packages/backend/server/src/event/events.ts b/packages/backend/server/src/event/events.ts new file mode 100644 index 0000000000..e98bb72440 --- /dev/null +++ b/packages/backend/server/src/event/events.ts @@ -0,0 +1,28 @@ +import type { Snapshot, User, Workspace } from '@prisma/client'; + +import { ChangePayload, Flatten, Payload } from './types'; + +interface EventDefinitions { + user: { + created: Payload; + updated: Payload>; + deleted: Payload; + }; + + workspace: { + created: Payload; + updated: Payload>; + deleted: Payload; + }; + + snapshot: { + created: Payload; + updated: Payload>; + deleted: Payload>; + }; +} + +export type EventKV = Flatten; + +export type Event = keyof EventKV; +export type EventPayload = EventKV[E]; diff --git a/packages/backend/server/src/event/index.ts b/packages/backend/server/src/event/index.ts new file mode 100644 index 0000000000..06b398ad04 --- /dev/null +++ b/packages/backend/server/src/event/index.ts @@ -0,0 +1,44 @@ +import { Global, Injectable, Module } from '@nestjs/common'; +import { + EventEmitter2, + EventEmitterModule, + OnEvent as RawOnEvent, +} from '@nestjs/event-emitter'; + +import { Event, EventPayload } from './events'; + +@Injectable() +export class EventEmitter { + constructor(private readonly emitter: EventEmitter2) {} + + emit(event: E, payload: EventPayload) { + return this.emitter.emit(event, payload); + } + + emitAsync(event: E, payload: EventPayload) { + return this.emitter.emitAsync(event, payload); + } + + on(event: E, handler: (payload: EventPayload) => void) { + return this.emitter.on(event, handler); + } + + once(event: E, handler: (payload: EventPayload) => void) { + return this.emitter.once(event, handler); + } +} + +export const OnEvent = ( + event: Event, + opts?: Parameters[1] +) => { + return RawOnEvent(event, opts); +}; + +@Global() +@Module({ + imports: [EventEmitterModule.forRoot()], + providers: [EventEmitter], + exports: [EventEmitter], +}) +export class EventModule {} diff --git a/packages/backend/server/src/event/types.ts b/packages/backend/server/src/event/types.ts new file mode 100644 index 0000000000..2a4bb5771c --- /dev/null +++ b/packages/backend/server/src/event/types.ts @@ -0,0 +1,38 @@ +export type Payload = { + __payload: true; + data: T; +}; + +export type ChangePayload = { + from: Partial; + to: Partial; +}; + +export type Join = A extends '' + ? B + : `${A}.${B}`; + +export type PathType = string extends Path + ? unknown + : Path extends keyof T + ? T[Path] + : Path extends `${infer K}.${infer R}` + ? K extends keyof T + ? PathType + : unknown + : unknown; + +export type Leaves = T extends Payload + ? P + : T extends Record + ? { + [K in keyof T]: K extends string ? Leaves> : never; + }[keyof T] + : never; + +export type Flatten = Leaves extends infer R + ? { + // @ts-expect-error yo, ts can't make it + [K in R]: PathType extends Payload ? { data: U } : never; + } + : never; diff --git a/packages/backend/server/src/modules/index.ts b/packages/backend/server/src/modules/index.ts index d021dd8bfa..422b276265 100644 --- a/packages/backend/server/src/modules/index.ts +++ b/packages/backend/server/src/modules/index.ts @@ -1,5 +1,4 @@ import { DynamicModule, Type } from '@nestjs/common'; -import { EventEmitterModule } from '@nestjs/event-emitter'; import { ScheduleModule } from '@nestjs/schedule'; import { GqlModule } from '../graphql.module'; @@ -11,11 +10,7 @@ import { SyncModule } from './sync'; import { UsersModule } from './users'; import { WorkspaceModule } from './workspaces'; -const BusinessModules: (Type | DynamicModule)[] = [ - EventEmitterModule.forRoot({ - global: true, - }), -]; +const BusinessModules: (Type | DynamicModule)[] = []; switch (SERVER_FLAVOR) { case 'sync':