feat(server): events system (#5145)
This commit is contained in:
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
|||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { CacheModule } from './cache';
|
import { CacheModule } from './cache';
|
||||||
import { ConfigModule } from './config';
|
import { ConfigModule } from './config';
|
||||||
|
import { EventModule } from './event';
|
||||||
import { BusinessModules } from './modules';
|
import { BusinessModules } from './modules';
|
||||||
import { AuthModule } from './modules/auth';
|
import { AuthModule } from './modules/auth';
|
||||||
import { PrismaModule } from './prisma';
|
import { PrismaModule } from './prisma';
|
||||||
@@ -14,6 +15,7 @@ const BasicModules = [
|
|||||||
PrismaModule,
|
PrismaModule,
|
||||||
ConfigModule.forRoot(),
|
ConfigModule.forRoot(),
|
||||||
CacheModule,
|
CacheModule,
|
||||||
|
EventModule,
|
||||||
StorageModule.forRoot(),
|
StorageModule.forRoot(),
|
||||||
SessionModule,
|
SessionModule,
|
||||||
RateLimiterModule,
|
RateLimiterModule,
|
||||||
|
|||||||
28
packages/backend/server/src/event/events.ts
Normal file
28
packages/backend/server/src/event/events.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import type { Snapshot, User, Workspace } from '@prisma/client';
|
||||||
|
|
||||||
|
import { ChangePayload, Flatten, Payload } from './types';
|
||||||
|
|
||||||
|
interface EventDefinitions {
|
||||||
|
user: {
|
||||||
|
created: Payload<User>;
|
||||||
|
updated: Payload<ChangePayload<User>>;
|
||||||
|
deleted: Payload<User['id']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
workspace: {
|
||||||
|
created: Payload<Workspace>;
|
||||||
|
updated: Payload<ChangePayload<Workspace>>;
|
||||||
|
deleted: Payload<Workspace['id']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
snapshot: {
|
||||||
|
created: Payload<Snapshot>;
|
||||||
|
updated: Payload<ChangePayload<Snapshot>>;
|
||||||
|
deleted: Payload<Pick<Snapshot, 'id' | 'workspaceId'>>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventKV = Flatten<EventDefinitions>;
|
||||||
|
|
||||||
|
export type Event = keyof EventKV;
|
||||||
|
export type EventPayload<E extends Event> = EventKV[E];
|
||||||
44
packages/backend/server/src/event/index.ts
Normal file
44
packages/backend/server/src/event/index.ts
Normal file
@@ -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<E extends Event>(event: E, payload: EventPayload<E>) {
|
||||||
|
return this.emitter.emit(event, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitAsync<E extends Event>(event: E, payload: EventPayload<E>) {
|
||||||
|
return this.emitter.emitAsync(event, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
on<E extends Event>(event: E, handler: (payload: EventPayload<E>) => void) {
|
||||||
|
return this.emitter.on(event, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
once<E extends Event>(event: E, handler: (payload: EventPayload<E>) => void) {
|
||||||
|
return this.emitter.once(event, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OnEvent = (
|
||||||
|
event: Event,
|
||||||
|
opts?: Parameters<typeof RawOnEvent>[1]
|
||||||
|
) => {
|
||||||
|
return RawOnEvent(event, opts);
|
||||||
|
};
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [EventEmitterModule.forRoot()],
|
||||||
|
providers: [EventEmitter],
|
||||||
|
exports: [EventEmitter],
|
||||||
|
})
|
||||||
|
export class EventModule {}
|
||||||
38
packages/backend/server/src/event/types.ts
Normal file
38
packages/backend/server/src/event/types.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
export type Payload<T> = {
|
||||||
|
__payload: true;
|
||||||
|
data: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChangePayload<T> = {
|
||||||
|
from: Partial<T>;
|
||||||
|
to: Partial<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Join<A extends string, B extends string> = A extends ''
|
||||||
|
? B
|
||||||
|
: `${A}.${B}`;
|
||||||
|
|
||||||
|
export type PathType<T, Path extends string> = string extends Path
|
||||||
|
? unknown
|
||||||
|
: Path extends keyof T
|
||||||
|
? T[Path]
|
||||||
|
: Path extends `${infer K}.${infer R}`
|
||||||
|
? K extends keyof T
|
||||||
|
? PathType<T[K], R>
|
||||||
|
: unknown
|
||||||
|
: unknown;
|
||||||
|
|
||||||
|
export type Leaves<T, P extends string = ''> = T extends Payload<any>
|
||||||
|
? P
|
||||||
|
: T extends Record<string, any>
|
||||||
|
? {
|
||||||
|
[K in keyof T]: K extends string ? Leaves<T[K], Join<P, K>> : never;
|
||||||
|
}[keyof T]
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type Flatten<T> = Leaves<T> extends infer R
|
||||||
|
? {
|
||||||
|
// @ts-expect-error yo, ts can't make it
|
||||||
|
[K in R]: PathType<T, K> extends Payload<infer U> ? { data: U } : never;
|
||||||
|
}
|
||||||
|
: never;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { DynamicModule, Type } from '@nestjs/common';
|
import { DynamicModule, Type } from '@nestjs/common';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
|
||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
|
|
||||||
import { GqlModule } from '../graphql.module';
|
import { GqlModule } from '../graphql.module';
|
||||||
@@ -11,11 +10,7 @@ import { SyncModule } from './sync';
|
|||||||
import { UsersModule } from './users';
|
import { UsersModule } from './users';
|
||||||
import { WorkspaceModule } from './workspaces';
|
import { WorkspaceModule } from './workspaces';
|
||||||
|
|
||||||
const BusinessModules: (Type | DynamicModule)[] = [
|
const BusinessModules: (Type | DynamicModule)[] = [];
|
||||||
EventEmitterModule.forRoot({
|
|
||||||
global: true,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
switch (SERVER_FLAVOR) {
|
switch (SERVER_FLAVOR) {
|
||||||
case 'sync':
|
case 'sync':
|
||||||
|
|||||||
Reference in New Issue
Block a user