feat(shared): add shared module with domain primitives, infrastructure services, and utils
Domain primitives: BaseEntity, AggregateRoot, ValueObject, DomainEvent, Result<T,E> Infrastructure: PrismaService, RedisService, LoggerService (pino), EventBusService Utils: Vietnam phone validator/normalizer, VND currency formatter, Vietnamese slug generator Includes 45 unit tests covering all domain primitives, validators, and formatters. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { type EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { type DomainEvent } from '../domain/domain-event';
|
||||
|
||||
@Injectable()
|
||||
export class EventBusService {
|
||||
constructor(private readonly eventEmitter: EventEmitter2) {}
|
||||
|
||||
publish(event: DomainEvent): void {
|
||||
this.eventEmitter.emit(event.eventName, event);
|
||||
}
|
||||
|
||||
publishAll(events: DomainEvent[]): void {
|
||||
for (const event of events) {
|
||||
this.publish(event);
|
||||
}
|
||||
}
|
||||
|
||||
async publishAsync(event: DomainEvent): Promise<void> {
|
||||
await this.eventEmitter.emitAsync(event.eventName, event);
|
||||
}
|
||||
}
|
||||
4
apps/api/src/modules/shared/infrastructure/index.ts
Normal file
4
apps/api/src/modules/shared/infrastructure/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { PrismaService } from './prisma.service';
|
||||
export { RedisService } from './redis.service';
|
||||
export { LoggerService } from './logger.service';
|
||||
export { EventBusService } from './event-bus.service';
|
||||
41
apps/api/src/modules/shared/infrastructure/logger.service.ts
Normal file
41
apps/api/src/modules/shared/infrastructure/logger.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable, type LoggerService as NestLoggerService } from '@nestjs/common';
|
||||
import pino, { type Logger } from 'pino';
|
||||
|
||||
@Injectable()
|
||||
export class LoggerService implements NestLoggerService {
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor() {
|
||||
this.logger = pino({
|
||||
level: process.env['LOG_LEVEL'] ?? 'info',
|
||||
transport:
|
||||
process.env['NODE_ENV'] !== 'production'
|
||||
? { target: 'pino-pretty', options: { colorize: true } }
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
log(message: string, context?: string): void {
|
||||
this.logger.info({ context }, message);
|
||||
}
|
||||
|
||||
error(message: string, trace?: string, context?: string): void {
|
||||
this.logger.error({ context, trace }, message);
|
||||
}
|
||||
|
||||
warn(message: string, context?: string): void {
|
||||
this.logger.warn({ context }, message);
|
||||
}
|
||||
|
||||
debug(message: string, context?: string): void {
|
||||
this.logger.debug({ context }, message);
|
||||
}
|
||||
|
||||
verbose(message: string, context?: string): void {
|
||||
this.logger.trace({ context }, message);
|
||||
}
|
||||
|
||||
child(bindings: Record<string, unknown>): Logger {
|
||||
return this.logger.child(bindings);
|
||||
}
|
||||
}
|
||||
13
apps/api/src/modules/shared/infrastructure/prisma.service.ts
Normal file
13
apps/api/src/modules/shared/infrastructure/prisma.service.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Injectable, type OnModuleInit, type OnModuleDestroy } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
async onModuleInit(): Promise<void> {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
40
apps/api/src/modules/shared/infrastructure/redis.service.ts
Normal file
40
apps/api/src/modules/shared/infrastructure/redis.service.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Injectable, type OnModuleDestroy } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
@Injectable()
|
||||
export class RedisService implements OnModuleDestroy {
|
||||
private readonly client: Redis;
|
||||
|
||||
constructor() {
|
||||
this.client = new Redis({
|
||||
host: process.env['REDIS_HOST'] ?? 'localhost',
|
||||
port: Number(process.env['REDIS_PORT'] ?? 6379),
|
||||
password: process.env['REDIS_PASSWORD'] ?? undefined,
|
||||
lazyConnect: true,
|
||||
});
|
||||
}
|
||||
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
await this.client.quit();
|
||||
}
|
||||
|
||||
getClient(): Redis {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
async get(key: string): Promise<string | null> {
|
||||
return this.client.get(key);
|
||||
}
|
||||
|
||||
async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
|
||||
if (ttlSeconds) {
|
||||
await this.client.set(key, value, 'EX', ttlSeconds);
|
||||
} else {
|
||||
await this.client.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
await this.client.del(key);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user