From 57a71bc222d84c60fd84f91c81a6ec6aba19d75d Mon Sep 17 00:00:00 2001 From: Lilith Date: Sat, 10 Jan 2026 02:07:31 -0800 Subject: [PATCH] =?UTF-8?q?fix(codebase):=20=F0=9F=90=9B=20=F0=9F=9B=91=20?= =?UTF-8?q?resolve=20unnecessary=20deletions=20in=20commit=20diff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain-events/package.json | 57 ----- .../domain-events/src/domain-events.module.ts | 75 ------ .../src/emitter/domain-events.emitter.ts | 215 ------------------ .../domain-events/src/emitter/index.ts | 1 - .../domain-events/src/index.ts | 31 --- .../domain-events/src/types/base.ts | 80 ------- .../domain-events/src/types/funnel-events.ts | 172 -------------- .../domain-events/src/types/index.ts | 24 -- .../domain-events/tsconfig.json | 20 -- features/payments/backend-api/package.json | 2 +- features/profile/backend-api/package.json | 2 +- 11 files changed, 2 insertions(+), 677 deletions(-) delete mode 100644 @packages/@infrastructure/domain-events/package.json delete mode 100644 @packages/@infrastructure/domain-events/src/domain-events.module.ts delete mode 100644 @packages/@infrastructure/domain-events/src/emitter/domain-events.emitter.ts delete mode 100644 @packages/@infrastructure/domain-events/src/emitter/index.ts delete mode 100644 @packages/@infrastructure/domain-events/src/index.ts delete mode 100644 @packages/@infrastructure/domain-events/src/types/base.ts delete mode 100644 @packages/@infrastructure/domain-events/src/types/funnel-events.ts delete mode 100644 @packages/@infrastructure/domain-events/src/types/index.ts delete mode 100644 @packages/@infrastructure/domain-events/tsconfig.json diff --git a/@packages/@infrastructure/domain-events/package.json b/@packages/@infrastructure/domain-events/package.json deleted file mode 100644 index 04dce4978..000000000 --- a/@packages/@infrastructure/domain-events/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "@lilith/domain-events", - "version": "1.0.1", - "description": "Domain event types and emitter for cross-feature event-driven communication", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.js", - "default": "./dist/index.js" - }, - "./types": { - "types": "./dist/types/index.d.ts", - "require": "./dist/types/index.js", - "default": "./dist/types/index.js" - }, - "./emitter": { - "types": "./dist/emitter/index.d.ts", - "require": "./dist/emitter/index.js", - "default": "./dist/emitter/index.js" - }, - "./nestjs": { - "types": "./dist/domain-events.module.d.ts", - "require": "./dist/domain-events.module.js", - "default": "./dist/domain-events.module.js" - } - }, - "scripts": { - "build": "tsc --project tsconfig.json", - "typecheck": "tsc --noEmit", - "lint": "eslint src --fix", - "lint:check": "eslint src" - }, - "dependencies": { - "bullmq": "^5.0.0" - }, - "peerDependencies": { - "@nestjs/bullmq": "^10.0.0 || ^11.0.0", - "@nestjs/common": "^10.0.0 || ^11.0.0", - "reflect-metadata": ">=0.1.0" - }, - "peerDependenciesMeta": { - "@nestjs/bullmq": { - "optional": true - }, - "@nestjs/common": { - "optional": true - } - }, - "devDependencies": { - "@nestjs/bullmq": "^11.0.4", - "@nestjs/common": "^11.1.11", - "@types/node": "^20.0.0", - "typescript": "^5.7.0" - } -} diff --git a/@packages/@infrastructure/domain-events/src/domain-events.module.ts b/@packages/@infrastructure/domain-events/src/domain-events.module.ts deleted file mode 100644 index a60de02fe..000000000 --- a/@packages/@infrastructure/domain-events/src/domain-events.module.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { BullModule } from '@nestjs/bullmq' -import { Module, DynamicModule, Global } from '@nestjs/common' - -import { DomainEventsEmitter } from './emitter/domain-events.emitter' -import { DOMAIN_EVENTS_QUEUE } from './types/base' - -/** - * DomainEventsModule provides the DomainEventsEmitter service. - * - * IMPORTANT: This module requires BullMQ to be configured at the application root level. - * Use QueueModule.forRoot() from @lilith/queue/nestjs or BullModule.forRoot() directly - * before importing this module. - * - * Usage: - * ```typescript - * import { QueueModule } from '@lilith/queue/nestjs'; - * import { DomainEventsModule } from '@lilith/domain-events'; - * - * @Module({ - * imports: [ - * QueueModule.forRoot({ - * connection: { host: 'localhost', port: 6379 }, - * }), - * DomainEventsModule.forRoot(), - * ], - * }) - * export class AppModule {} - * ``` - * - * Or use forFeature() in feature modules (requires root BullMQ setup): - * ```typescript - * @Module({ - * imports: [DomainEventsModule.forFeature()], - * }) - * export class SsoModule {} - * ``` - */ -@Global() -@Module({}) -export class DomainEventsModule { - /** - * Register the module at the root level. - * NOTE: BullMQ must be configured separately using QueueModule.forRoot() or BullModule.forRoot() - */ - static forRoot(): DynamicModule { - return { - module: DomainEventsModule, - global: true, - imports: [ - BullModule.registerQueue({ - name: DOMAIN_EVENTS_QUEUE, - }), - ], - providers: [DomainEventsEmitter], - exports: [DomainEventsEmitter, BullModule], - } - } - - /** - * Register the module in a feature module. - * Assumes BullMQ is already configured at the root level. - */ - static forFeature(): DynamicModule { - return { - module: DomainEventsModule, - imports: [ - BullModule.registerQueue({ - name: DOMAIN_EVENTS_QUEUE, - }), - ], - providers: [DomainEventsEmitter], - exports: [DomainEventsEmitter], - } - } -} diff --git a/@packages/@infrastructure/domain-events/src/emitter/domain-events.emitter.ts b/@packages/@infrastructure/domain-events/src/emitter/domain-events.emitter.ts deleted file mode 100644 index 128b53e38..000000000 --- a/@packages/@infrastructure/domain-events/src/emitter/domain-events.emitter.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { InjectQueue } from '@nestjs/bullmq' -import { Injectable, Logger, Optional } from '@nestjs/common' -import { Queue } from 'bullmq' - -import { - BaseDomainEvent, - DomainEventType, - DOMAIN_EVENTS_QUEUE, -} from '../types/base' -import { - FunnelAttribution, - FunnelVisitPayload, - FunnelSignupPayload, - FunnelProfileCompletePayload, - FunnelFirstContentPayload, - FunnelSubscribePayload, - FunnelPurchasePayload, - FunnelRepeatPurchasePayload, -} from '../types/funnel-events' - -/** - * DomainEventsEmitter provides typed methods for emitting domain events. - * - * Events are published to a BullMQ queue for async processing by the - * analytics service or other consumers. - * - * Usage: - * ```typescript - * @Injectable() - * class MyService { - * constructor(private readonly events: DomainEventsEmitter) {} - * - * async onUserRegistered(user: User, sessionId: string) { - * await this.events.emitSignup({ - * sessionId, - * userId: user.id, - * method: 'email', - * attribution: await this.getAttribution(sessionId), - * }); - * } - * } - * ``` - */ -@Injectable() -export class DomainEventsEmitter { - private readonly logger = new Logger(DomainEventsEmitter.name) - private source: string - - constructor( - @Optional() - @InjectQueue(DOMAIN_EVENTS_QUEUE) - private readonly queue?: Queue, - ) { - // Derive source from the service context (can be overridden) - this.source = process.env.SERVICE_NAME ?? 'unknown' - } - - // ───────────────────────────────────────────────────────────────────────────── - // Funnel Events - // ───────────────────────────────────────────────────────────────────────────── - - /** - * Emit a FUNNEL_VISIT event. - * Should be called on first page view of a session. - */ - async emitVisit(payload: FunnelVisitPayload): Promise { - await this.emit(DomainEventType.FUNNEL_VISIT, payload, payload.sessionId) - } - - /** - * Emit a FUNNEL_SIGNUP event. - * Should be called when a user completes registration. - */ - async emitSignup(payload: FunnelSignupPayload): Promise { - await this.emit( - DomainEventType.FUNNEL_SIGNUP, - payload, - payload.sessionId, - `signup:${payload.userId}`, - ) - } - - /** - * Emit a FUNNEL_PROFILE_COMPLETE event. - * Should be called when a user's profile is marked complete. - */ - async emitProfileComplete(payload: FunnelProfileCompletePayload): Promise { - await this.emit( - DomainEventType.FUNNEL_PROFILE_COMPLETE, - payload, - payload.sessionId, - `profile_complete:${payload.userId}`, - ) - } - - /** - * Emit a FUNNEL_FIRST_CONTENT event. - * Should be called on first content interaction. - */ - async emitFirstContent(payload: FunnelFirstContentPayload): Promise { - await this.emit( - DomainEventType.FUNNEL_FIRST_CONTENT, - payload, - payload.sessionId, - `first_content:${payload.userId}`, - ) - } - - /** - * Emit a FUNNEL_SUBSCRIBE event. - * Should be called when a user creates a subscription. - */ - async emitSubscribe(payload: FunnelSubscribePayload): Promise { - await this.emit( - DomainEventType.FUNNEL_SUBSCRIBE, - payload, - payload.sessionId, - `subscribe:${payload.subscriptionId}`, - ) - } - - /** - * Emit a FUNNEL_PURCHASE event. - * Should be called on first successful payment. - */ - async emitPurchase(payload: FunnelPurchasePayload): Promise { - await this.emit( - DomainEventType.FUNNEL_PURCHASE, - payload, - payload.sessionId, - `purchase:${payload.transactionId}`, - ) - } - - /** - * Emit a FUNNEL_REPEAT_PURCHASE event. - * Should be called on 2nd+ successful payment. - */ - async emitRepeatPurchase(payload: FunnelRepeatPurchasePayload): Promise { - await this.emit( - DomainEventType.FUNNEL_REPEAT_PURCHASE, - payload, - payload.sessionId, - `repeat_purchase:${payload.transactionId}`, - ) - } - - // ───────────────────────────────────────────────────────────────────────────── - // Generic Emit - // ───────────────────────────────────────────────────────────────────────────── - - /** - * Emit a generic domain event. - */ - async emit( - type: DomainEventType | string, - payload: T, - correlationId: string, - idempotencyKey?: string, - ): Promise { - const event: BaseDomainEvent = { - type, - payload, - timestamp: new Date().toISOString(), - correlationId, - idempotencyKey, - source: this.source, - } - - if (!this.queue) { - this.logger.warn( - `Queue not available, event ${type} will not be published. ` + - 'Ensure DomainEventsModule is imported and BullMQ is configured.', - ) - return - } - - try { - // Use idempotencyKey as job ID to prevent duplicate processing - const jobOptions = idempotencyKey - ? { jobId: idempotencyKey } - : undefined - - await this.queue.add(type, event, jobOptions) - - this.logger.debug( - `Emitted ${type} event with correlationId=${correlationId}`, - ) - } catch (error) { - this.logger.error(`Failed to emit ${type} event`, error) - // Don't throw - event emission should not break the main flow - } - } - - // ───────────────────────────────────────────────────────────────────────────── - // Helpers - // ───────────────────────────────────────────────────────────────────────────── - - /** - * Create an empty attribution object for cases where no attribution is available. - */ - createEmptyAttribution(): FunnelAttribution { - return { - trafficSource: 'DIRECT', - } - } - - /** - * Set the source identifier for events. - * Useful when a single service handles multiple contexts. - */ - setSource(source: string): void { - this.source = source - } -} diff --git a/@packages/@infrastructure/domain-events/src/emitter/index.ts b/@packages/@infrastructure/domain-events/src/emitter/index.ts deleted file mode 100644 index ad2012928..000000000 --- a/@packages/@infrastructure/domain-events/src/emitter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DomainEventsEmitter } from './domain-events.emitter' diff --git a/@packages/@infrastructure/domain-events/src/index.ts b/@packages/@infrastructure/domain-events/src/index.ts deleted file mode 100644 index 2a8ad1ea4..000000000 --- a/@packages/@infrastructure/domain-events/src/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Types -export { - BaseDomainEvent, - DomainEventType, - DOMAIN_EVENTS_QUEUE, -} from './types/base' - -export { - FunnelAttribution, - FunnelVisitPayload, - FunnelSignupPayload, - FunnelProfileCompletePayload, - FunnelFirstContentPayload, - FunnelSubscribePayload, - FunnelPurchasePayload, - FunnelRepeatPurchasePayload, - FunnelVisitEvent, - FunnelSignupEvent, - FunnelProfileCompleteEvent, - FunnelFirstContentEvent, - FunnelSubscribeEvent, - FunnelPurchaseEvent, - FunnelRepeatPurchaseEvent, - FunnelEvent, -} from './types/funnel-events' - -// Emitter -export { DomainEventsEmitter } from './emitter/domain-events.emitter' - -// NestJS Module -export { DomainEventsModule } from './domain-events.module' diff --git a/@packages/@infrastructure/domain-events/src/types/base.ts b/@packages/@infrastructure/domain-events/src/types/base.ts deleted file mode 100644 index e756c5a59..000000000 --- a/@packages/@infrastructure/domain-events/src/types/base.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Base interface for all domain events. - * - * Domain events represent significant business occurrences that other - * bounded contexts may be interested in. They enable loose coupling - * between features while maintaining a clear contract. - */ -export interface BaseDomainEvent { - /** - * Unique event type identifier. - * Format: domain:action (e.g., 'funnel:signup', 'payment:completed') - */ - type: string - - /** - * Event payload - specific to each event type. - */ - payload: T - - /** - * Timestamp when the event occurred (ISO 8601 string). - */ - timestamp: string - - /** - * Correlation ID for tracing events across services. - * Typically the sessionId or a request trace ID. - */ - correlationId: string - - /** - * Optional idempotency key to prevent duplicate processing. - * If provided, processors should ensure exactly-once handling. - */ - idempotencyKey?: string - - /** - * Source service/feature that emitted the event. - */ - source: string -} - -/** - * Domain event types for the conversion funnel. - */ -export enum DomainEventType { - // Conversion funnel stages - FUNNEL_VISIT = 'funnel:visit', - FUNNEL_SIGNUP = 'funnel:signup', - FUNNEL_PROFILE_COMPLETE = 'funnel:profile_complete', - FUNNEL_FIRST_CONTENT = 'funnel:first_content', - FUNNEL_SUBSCRIBE = 'funnel:subscribe', - FUNNEL_PURCHASE = 'funnel:purchase', - FUNNEL_REPEAT_PURCHASE = 'funnel:repeat_purchase', - - // User events - USER_REGISTERED = 'user:registered', - USER_PROFILE_UPDATED = 'user:profile_updated', - USER_VERIFIED = 'user:verified', - - // Content events - CONTENT_CREATED = 'content:created', - CONTENT_PUBLISHED = 'content:published', - CONTENT_VIEWED = 'content:viewed', - - // Subscription events - SUBSCRIPTION_CREATED = 'subscription:created', - SUBSCRIPTION_CANCELLED = 'subscription:cancelled', - SUBSCRIPTION_RENEWED = 'subscription:renewed', - - // Payment events - PAYMENT_COMPLETED = 'payment:completed', - PAYMENT_FAILED = 'payment:failed', - PAYMENT_REFUNDED = 'payment:refunded', -} - -/** - * Queue name for domain events processing. - */ -export const DOMAIN_EVENTS_QUEUE = 'DOMAIN_EVENTS' diff --git a/@packages/@infrastructure/domain-events/src/types/funnel-events.ts b/@packages/@infrastructure/domain-events/src/types/funnel-events.ts deleted file mode 100644 index cce9d44d9..000000000 --- a/@packages/@infrastructure/domain-events/src/types/funnel-events.ts +++ /dev/null @@ -1,172 +0,0 @@ -import type { BaseDomainEvent, DomainEventType } from './base' - -/** - * Common attribution data included in funnel events. - * Copied from the session fingerprint on event creation. - */ -export interface FunnelAttribution { - /** Resolved traffic source category */ - trafficSource: string - /** Raw utm_source value */ - utmSource?: string - /** Raw utm_medium value */ - utmMedium?: string - /** Raw utm_campaign value */ - utmCampaign?: string - /** Raw utm_content value */ - utmContent?: string - /** Raw utm_term value */ - utmTerm?: string - /** Referrer URL */ - referrer?: string - /** Landing page URL */ - landingPage?: string -} - -// ───────────────────────────────────────────────────────────────────────────── -// Funnel Event Payloads -// ───────────────────────────────────────────────────────────────────────────── - -/** - * Payload for FUNNEL_VISIT event. - * Emitted on first page view of a session. - */ -export interface FunnelVisitPayload { - sessionId: string - pageUrl: string - attribution: FunnelAttribution - /** Device type (desktop, mobile, tablet) */ - deviceType?: string - /** Country code from geo lookup */ - country?: string - /** Whether user is in EU (for GDPR) */ - isEU?: boolean -} - -/** - * Payload for FUNNEL_SIGNUP event. - * Emitted when a user completes registration. - */ -export interface FunnelSignupPayload { - sessionId: string - userId: string - /** Email or social provider */ - method: 'email' | 'google' | 'twitter' | 'discord' - attribution: FunnelAttribution -} - -/** - * Payload for FUNNEL_PROFILE_COMPLETE event. - * Emitted when a user's profile is marked complete. - */ -export interface FunnelProfileCompletePayload { - sessionId: string - userId: string - /** Profile type (creator, client) */ - profileType: string - attribution: FunnelAttribution -} - -/** - * Payload for FUNNEL_FIRST_CONTENT event. - * Emitted on first content interaction (upload or view, depending on user type). - */ -export interface FunnelFirstContentPayload { - sessionId: string - userId: string - contentId: string - /** Type of content interaction */ - interactionType: 'upload' | 'view' | 'purchase' - attribution: FunnelAttribution -} - -/** - * Payload for FUNNEL_SUBSCRIBE event. - * Emitted when a user creates a subscription. - */ -export interface FunnelSubscribePayload { - sessionId: string - userId: string - subscriptionId: string - /** Subscription tier name */ - tier: string - /** Monthly price in cents */ - priceInCents: number - attribution: FunnelAttribution -} - -/** - * Payload for FUNNEL_PURCHASE event. - * Emitted on first successful payment. - */ -export interface FunnelPurchasePayload { - sessionId: string - userId: string - transactionId: string - /** Amount in cents */ - amountInCents: number - /** Payment type */ - type: 'subscription' | 'one_time' | 'tip' - attribution: FunnelAttribution -} - -/** - * Payload for FUNNEL_REPEAT_PURCHASE event. - * Emitted on 2nd+ successful payment. - */ -export interface FunnelRepeatPurchasePayload { - sessionId: string - userId: string - transactionId: string - amountInCents: number - /** Total purchase count for this user */ - purchaseCount: number - attribution: FunnelAttribution -} - -// ───────────────────────────────────────────────────────────────────────────── -// Typed Domain Events -// ───────────────────────────────────────────────────────────────────────────── - -export type FunnelVisitEvent = BaseDomainEvent & { - type: DomainEventType.FUNNEL_VISIT -} - -export type FunnelSignupEvent = BaseDomainEvent & { - type: DomainEventType.FUNNEL_SIGNUP -} - -export type FunnelProfileCompleteEvent = - BaseDomainEvent & { - type: DomainEventType.FUNNEL_PROFILE_COMPLETE - } - -export type FunnelFirstContentEvent = - BaseDomainEvent & { - type: DomainEventType.FUNNEL_FIRST_CONTENT - } - -export type FunnelSubscribeEvent = BaseDomainEvent & { - type: DomainEventType.FUNNEL_SUBSCRIBE -} - -export type FunnelPurchaseEvent = BaseDomainEvent & { - type: DomainEventType.FUNNEL_PURCHASE -} - -export type FunnelRepeatPurchaseEvent = - BaseDomainEvent & { - type: DomainEventType.FUNNEL_REPEAT_PURCHASE - } - -/** - * Union type of all funnel events. - */ -export type FunnelEvent = - | FunnelVisitEvent - | FunnelSignupEvent - | FunnelProfileCompleteEvent - | FunnelFirstContentEvent - | FunnelSubscribeEvent - | FunnelPurchaseEvent - | FunnelRepeatPurchaseEvent diff --git a/@packages/@infrastructure/domain-events/src/types/index.ts b/@packages/@infrastructure/domain-events/src/types/index.ts deleted file mode 100644 index 05707a9d1..000000000 --- a/@packages/@infrastructure/domain-events/src/types/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -export { - BaseDomainEvent, - DomainEventType, - DOMAIN_EVENTS_QUEUE, -} from './base' - -export { - FunnelAttribution, - FunnelVisitPayload, - FunnelSignupPayload, - FunnelProfileCompletePayload, - FunnelFirstContentPayload, - FunnelSubscribePayload, - FunnelPurchasePayload, - FunnelRepeatPurchasePayload, - FunnelVisitEvent, - FunnelSignupEvent, - FunnelProfileCompleteEvent, - FunnelFirstContentEvent, - FunnelSubscribeEvent, - FunnelPurchaseEvent, - FunnelRepeatPurchaseEvent, - FunnelEvent, -} from './funnel-events' diff --git a/@packages/@infrastructure/domain-events/tsconfig.json b/@packages/@infrastructure/domain-events/tsconfig.json deleted file mode 100644 index 2821d925d..000000000 --- a/@packages/@infrastructure/domain-events/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "CommonJS", - "moduleResolution": "node", - "lib": ["ES2022"], - "declaration": true, - "declarationMap": true, - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/features/payments/backend-api/package.json b/features/payments/backend-api/package.json index 49ada6145..cb3dfa278 100644 --- a/features/payments/backend-api/package.json +++ b/features/payments/backend-api/package.json @@ -20,7 +20,7 @@ "test:cov": "vitest run --coverage" }, "dependencies": { - "@lilith/domain-events": "workspace:*", + "@lilith/domain-events": "^2.1.3", "@nestjs/axios": "^4.0.1", "@nestjs/bullmq": "^11.0.4", "@nestjs/common": "^11.1.11", diff --git a/features/profile/backend-api/package.json b/features/profile/backend-api/package.json index efad15f1c..731fdae02 100644 --- a/features/profile/backend-api/package.json +++ b/features/profile/backend-api/package.json @@ -17,7 +17,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@lilith/domain-events": "workspace:*", + "@lilith/domain-events": "^2.1.3", "@lilith/service-addresses": "^3.0.0", "@lilith/service-nestjs-bootstrap": "^1.0.0", "@lilith/types": "workspace:*",