@k-msg/webhook 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/dispatcher/batch.dispatcher.d.ts +68 -0
- package/dist/dispatcher/index.d.ts +9 -0
- package/dist/dispatcher/load-balancer.d.ts +104 -0
- package/dist/dispatcher/queue.manager.d.ts +80 -0
- package/dist/dispatcher/types.d.ts +49 -0
- package/dist/index.d.ts +18 -1134
- package/dist/index.js +43 -3016
- package/dist/index.js.map +89 -1
- package/dist/index.mjs +47 -0
- package/dist/index.mjs.map +89 -0
- package/dist/registry/delivery.store.d.ts +121 -0
- package/dist/registry/endpoint.manager.d.ts +92 -0
- package/dist/registry/event.store.d.ts +125 -0
- package/dist/registry/index.d.ts +9 -0
- package/dist/registry/types.d.ts +62 -0
- package/dist/retry/retry.manager.d.ts +61 -0
- package/dist/security/security.manager.d.ts +50 -0
- package/dist/services/webhook.dispatcher.d.ts +26 -0
- package/dist/services/webhook.registry.d.ts +16 -0
- package/dist/services/webhook.service.d.ts +78 -0
- package/dist/types/webhook.types.d.ts +219 -0
- package/package.json +18 -13
- package/dist/index.cjs +0 -3068
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1135
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types/webhook.types.ts","../src/services/webhook.dispatcher.ts","../src/services/webhook.registry.ts","../src/security/security.manager.ts","../src/retry/retry.manager.ts","../src/services/webhook.service.ts","../src/dispatcher/batch.dispatcher.ts","../src/dispatcher/queue.manager.ts","../src/dispatcher/load-balancer.ts","../src/registry/endpoint.manager.ts","../src/registry/delivery.store.ts","../src/registry/event.store.ts"],"sourcesContent":["/**\n * Webhook System\n * 실시간 메시지 이벤트 알림 시스템\n */\n\n// 핵심 서비스\nexport { WebhookService } from './services/webhook.service';\nexport { WebhookDispatcher } from './services/webhook.dispatcher';\nexport { WebhookRegistry } from './services/webhook.registry';\n\n// 디스패처 컴포넌트\nexport { BatchDispatcher } from './dispatcher/batch.dispatcher';\nexport { QueueManager } from './dispatcher/queue.manager';\nexport { LoadBalancer } from './dispatcher/load-balancer';\n\n// 레지스트리 컴포넌트\nexport { EndpointManager } from './registry/endpoint.manager';\nexport { DeliveryStore } from './registry/delivery.store';\nexport { EventStore } from './registry/event.store';\n\n// 보안 및 재시도 관리\nexport { SecurityManager } from './security/security.manager';\nexport { RetryManager } from './retry/retry.manager';\n\n// 타입 정의\nexport type {\n WebhookConfig,\n WebhookEvent,\n WebhookEndpoint,\n WebhookDelivery,\n WebhookStats,\n WebhookTestResult,\n WebhookEventData,\n WebhookEndpointData,\n WebhookDeliveryData,\n WebhookAttempt,\n WebhookBatch,\n WebhookSecurity\n} from './types/webhook.types';\n\n// 디스패처 타입\nexport type {\n DispatchConfig,\n BatchConfig,\n QueueConfig,\n LoadBalancerConfig,\n DispatchJob,\n CircuitBreakerState\n} from './dispatcher/types';\n\n// 레지스트리 타입\nexport type {\n EndpointFilter,\n DeliveryFilter,\n EventFilter,\n StorageConfig,\n PaginationOptions,\n SearchResult\n} from './registry/types';\n\n// Enum 정의\nexport { WebhookEventType } from './types/webhook.types';","import { z } from 'zod';\n\nexport interface WebhookConfig {\n // 재시도 설정\n maxRetries: number;\n retryDelayMs: number;\n maxDelayMs?: number;\n backoffMultiplier?: number;\n jitter?: boolean;\n \n // 네트워크 설정\n timeoutMs: number;\n \n // 보안 설정\n enableSecurity: boolean;\n secretKey?: string;\n algorithm?: 'sha256' | 'sha1';\n signatureHeader?: string;\n signaturePrefix?: string;\n \n // 이벤트 설정\n enabledEvents: WebhookEventType[];\n \n // 배치 처리 설정\n batchSize: number;\n batchTimeoutMs: number;\n}\n\nexport enum WebhookEventType {\n // 메시지 이벤트\n MESSAGE_SENT = 'message.sent',\n MESSAGE_DELIVERED = 'message.delivered',\n MESSAGE_FAILED = 'message.failed',\n MESSAGE_CLICKED = 'message.clicked',\n MESSAGE_READ = 'message.read',\n\n // 템플릿 이벤트\n TEMPLATE_CREATED = 'template.created',\n TEMPLATE_APPROVED = 'template.approved',\n TEMPLATE_REJECTED = 'template.rejected',\n TEMPLATE_UPDATED = 'template.updated',\n TEMPLATE_DELETED = 'template.deleted',\n\n // 채널 이벤트\n CHANNEL_CREATED = 'channel.created',\n CHANNEL_VERIFIED = 'channel.verified',\n SENDER_NUMBER_ADDED = 'sender_number.added',\n SENDER_NUMBER_VERIFIED = 'sender_number.verified',\n\n // 시스템 이벤트\n QUOTA_WARNING = 'system.quota_warning',\n QUOTA_EXCEEDED = 'system.quota_exceeded',\n PROVIDER_ERROR = 'system.provider_error',\n SYSTEM_MAINTENANCE = 'system.maintenance',\n\n // 분석 이벤트\n ANOMALY_DETECTED = 'analytics.anomaly_detected',\n THRESHOLD_EXCEEDED = 'analytics.threshold_exceeded'\n}\n\nexport interface WebhookEvent<T = any> {\n id: string;\n type: WebhookEventType;\n timestamp: Date;\n data: T;\n metadata: {\n providerId?: string;\n channelId?: string;\n templateId?: string;\n messageId?: string;\n userId?: string;\n organizationId?: string;\n correlationId?: string;\n retryCount?: number;\n };\n version: string; // API 버전\n}\n\nexport interface WebhookEndpoint {\n id: string;\n url: string;\n name?: string;\n description?: string;\n active: boolean;\n events: WebhookEventType[];\n headers?: Record<string, string>;\n secret?: string;\n retryConfig?: {\n maxRetries: number;\n retryDelayMs: number;\n backoffMultiplier: number;\n };\n filters?: {\n providerId?: string[];\n channelId?: string[];\n templateId?: string[];\n };\n createdAt: Date;\n updatedAt: Date;\n lastTriggeredAt?: Date;\n status: 'active' | 'inactive' | 'error' | 'suspended';\n}\n\nexport interface WebhookDelivery {\n id: string;\n endpointId: string;\n eventId: string;\n url: string;\n httpMethod: 'POST' | 'PUT' | 'PATCH';\n headers: Record<string, string>;\n payload: string; // JSON stringified\n attempts: WebhookAttempt[];\n status: 'pending' | 'success' | 'failed' | 'exhausted';\n createdAt: Date;\n completedAt?: Date;\n nextRetryAt?: Date;\n}\n\nexport interface WebhookAttempt {\n attemptNumber: number;\n timestamp: Date;\n httpStatus?: number;\n responseBody?: string;\n responseHeaders?: Record<string, string>;\n error?: string;\n latencyMs: number;\n}\n\nexport interface WebhookSecurity {\n algorithm: 'sha256' | 'sha1';\n header: string; // 예: 'X-Webhook-Signature'\n prefix?: string; // 예: 'sha256='\n}\n\nexport interface WebhookBatch {\n id: string;\n endpointId: string;\n events: WebhookEvent[];\n createdAt: Date;\n scheduledAt: Date;\n status: 'pending' | 'processing' | 'completed' | 'failed';\n}\n\nexport interface WebhookStats {\n endpointId: string;\n timeRange: {\n start: Date;\n end: Date;\n };\n totalDeliveries: number;\n successfulDeliveries: number;\n failedDeliveries: number;\n averageLatencyMs: number;\n successRate: number;\n eventBreakdown: Record<WebhookEventType, number>;\n errorBreakdown: Record<string, number>;\n}\n\nexport interface WebhookTestResult {\n endpointId: string;\n url: string;\n success: boolean;\n httpStatus?: number;\n responseTime: number;\n error?: string;\n testedAt: Date;\n}\n\n// Zod Schemas\nexport const WebhookEventSchema = z.object({\n id: z.string(),\n type: z.string(),\n timestamp: z.date(),\n data: z.any(),\n metadata: z.object({\n providerId: z.string().optional(),\n channelId: z.string().optional(),\n templateId: z.string().optional(),\n messageId: z.string().optional(),\n userId: z.string().optional(),\n organizationId: z.string().optional(),\n correlationId: z.string().optional(),\n retryCount: z.number().optional(),\n }),\n version: z.string(),\n});\n\nexport const WebhookEndpointSchema = z.object({\n id: z.string(),\n url: z.string().url(),\n name: z.string().optional(),\n description: z.string().optional(),\n active: z.boolean(),\n events: z.array(z.string()),\n headers: z.record(z.string(), z.string()).optional(),\n secret: z.string().optional(),\n retryConfig: z.object({\n maxRetries: z.number().min(0).max(10),\n retryDelayMs: z.number().min(1000),\n backoffMultiplier: z.number().min(1).max(5),\n }).optional(),\n filters: z.object({\n providerId: z.array(z.string()).optional(),\n channelId: z.array(z.string()).optional(),\n templateId: z.array(z.string()).optional(),\n }).optional(),\n createdAt: z.date(),\n updatedAt: z.date(),\n lastTriggeredAt: z.date().optional(),\n status: z.enum(['active', 'inactive', 'error', 'suspended']),\n});\n\nexport const WebhookDeliverySchema = z.object({\n id: z.string(),\n endpointId: z.string(),\n eventId: z.string(),\n url: z.string().url(),\n httpMethod: z.enum(['POST', 'PUT', 'PATCH']),\n headers: z.record(z.string(), z.string()),\n payload: z.string(),\n attempts: z.array(z.object({\n attemptNumber: z.number(),\n timestamp: z.date(),\n httpStatus: z.number().optional(),\n responseBody: z.string().optional(),\n responseHeaders: z.record(z.string(), z.string()).optional(),\n error: z.string().optional(),\n latencyMs: z.number(),\n })),\n status: z.enum(['pending', 'success', 'failed', 'exhausted']),\n createdAt: z.date(),\n completedAt: z.date().optional(),\n nextRetryAt: z.date().optional(),\n});\n\nexport type WebhookEventData = z.infer<typeof WebhookEventSchema>;\nexport type WebhookEndpointData = z.infer<typeof WebhookEndpointSchema>;\nexport type WebhookDeliveryData = z.infer<typeof WebhookDeliverySchema>;","import type { WebhookConfig, WebhookEvent, WebhookEndpoint, WebhookDelivery, WebhookAttempt } from '../types/webhook.types';\n\nexport class WebhookDispatcher {\n private config: WebhookConfig;\n\n constructor(config: WebhookConfig) {\n this.config = config;\n }\n\n async dispatch(event: WebhookEvent, endpoint: WebhookEndpoint): Promise<WebhookDelivery> {\n const delivery: WebhookDelivery = {\n id: this.generateDeliveryId(),\n endpointId: endpoint.id,\n eventId: event.id,\n url: endpoint.url,\n httpMethod: 'POST',\n headers: this.buildHeaders(endpoint, event),\n payload: JSON.stringify(event),\n attempts: [],\n status: 'pending',\n createdAt: new Date(),\n };\n\n await this.executeDelivery(delivery, endpoint);\n return delivery;\n }\n\n private async executeDelivery(delivery: WebhookDelivery, endpoint: WebhookEndpoint): Promise<void> {\n const maxRetries = endpoint.retryConfig?.maxRetries || this.config.maxRetries;\n \n for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {\n const attemptResult = await this.makeHttpRequest(delivery, endpoint, attempt);\n delivery.attempts.push(attemptResult);\n\n if (attemptResult.httpStatus && attemptResult.httpStatus >= 200 && attemptResult.httpStatus < 300) {\n delivery.status = 'success';\n delivery.completedAt = new Date();\n return;\n }\n\n if (attempt <= maxRetries) {\n const delay = this.calculateRetryDelay(attempt, endpoint);\n await this.sleep(delay);\n }\n }\n\n delivery.status = 'exhausted';\n delivery.completedAt = new Date();\n }\n\n private async makeHttpRequest(delivery: WebhookDelivery, endpoint: WebhookEndpoint, attemptNumber: number): Promise<WebhookAttempt> {\n const startTime = Date.now();\n const attempt: WebhookAttempt = {\n attemptNumber,\n timestamp: new Date(),\n latencyMs: 0,\n };\n\n try {\n const response = await fetch(delivery.url, {\n method: delivery.httpMethod,\n headers: delivery.headers,\n body: delivery.payload,\n signal: AbortSignal.timeout(this.config.timeoutMs),\n });\n\n attempt.httpStatus = response.status;\n attempt.responseBody = await response.text();\n // Headers를 객체로 변환\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n attempt.responseHeaders = responseHeaders;\n attempt.latencyMs = Date.now() - startTime;\n\n if (!response.ok) {\n attempt.error = `HTTP ${response.status}: ${response.statusText}`;\n }\n\n } catch (error) {\n attempt.latencyMs = Date.now() - startTime;\n attempt.error = error instanceof Error ? error.message : 'Unknown error';\n }\n\n return attempt;\n }\n\n private buildHeaders(endpoint: WebhookEndpoint, event: WebhookEvent): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-Webhook-ID': event.id,\n 'X-Webhook-Event': event.type,\n 'X-Webhook-Timestamp': event.timestamp.toISOString(),\n 'User-Agent': 'K-Message-Webhook/1.0',\n };\n\n // 엔드포인트별 커스텀 헤더\n if (endpoint.headers) {\n Object.assign(headers, endpoint.headers);\n }\n\n // 보안 서명\n if (endpoint.secret) {\n const signature = this.generateSignature(JSON.stringify(event), endpoint.secret);\n headers['X-Webhook-Signature'] = signature;\n }\n\n return headers;\n }\n\n private generateSignature(payload: string, secret: string): string {\n // 실제 구현에서는 crypto 모듈 사용\n return `sha256=${Buffer.from(payload + secret).toString('base64')}`;\n }\n\n private calculateRetryDelay(attempt: number, endpoint: WebhookEndpoint): number {\n const baseDelay = endpoint.retryConfig?.retryDelayMs || this.config.retryDelayMs;\n const multiplier = endpoint.retryConfig?.backoffMultiplier || 2;\n return baseDelay * Math.pow(multiplier, attempt - 1);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n private generateDeliveryId(): string {\n return `delivery_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n }\n\n async shutdown(): Promise<void> {\n // 진행 중인 요청 정리\n }\n}","import type { WebhookEndpoint, WebhookDelivery, WebhookEventType } from '../types/webhook.types';\n\nexport class WebhookRegistry {\n private endpoints: Map<string, WebhookEndpoint> = new Map();\n private deliveries: Map<string, WebhookDelivery> = new Map();\n\n async addEndpoint(endpoint: WebhookEndpoint): Promise<void> {\n this.endpoints.set(endpoint.id, endpoint);\n }\n\n async updateEndpoint(endpointId: string, endpoint: WebhookEndpoint): Promise<void> {\n if (!this.endpoints.has(endpointId)) {\n throw new Error(`Endpoint ${endpointId} not found`);\n }\n this.endpoints.set(endpointId, endpoint);\n }\n\n async removeEndpoint(endpointId: string): Promise<void> {\n this.endpoints.delete(endpointId);\n }\n\n async getEndpoint(endpointId: string): Promise<WebhookEndpoint | null> {\n return this.endpoints.get(endpointId) || null;\n }\n\n async listEndpoints(): Promise<WebhookEndpoint[]> {\n return Array.from(this.endpoints.values());\n }\n\n async addDelivery(delivery: WebhookDelivery): Promise<void> {\n this.deliveries.set(delivery.id, delivery);\n }\n\n async getDeliveries(\n endpointId?: string,\n timeRange?: { start: Date; end: Date },\n eventType?: WebhookEventType,\n status?: string,\n limit = 100\n ): Promise<WebhookDelivery[]> {\n let deliveries = Array.from(this.deliveries.values());\n\n if (endpointId) {\n deliveries = deliveries.filter(d => d.endpointId === endpointId);\n }\n\n if (timeRange) {\n deliveries = deliveries.filter(d => \n d.createdAt >= timeRange.start && d.createdAt <= timeRange.end\n );\n }\n\n if (status) {\n deliveries = deliveries.filter(d => d.status === status);\n }\n\n return deliveries\n .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())\n .slice(0, limit);\n }\n\n async getFailedDeliveries(endpointId?: string, eventType?: WebhookEventType): Promise<WebhookDelivery[]> {\n return this.getDeliveries(endpointId, undefined, eventType, 'failed');\n }\n}","import * as crypto from 'crypto';\nimport type { WebhookConfig } from '../types/webhook.types';\n\nexport interface SecurityConfig {\n algorithm: 'sha256' | 'sha1';\n header: string;\n prefix?: string;\n}\n\n/**\n * Webhook 보안 관리자\n * 서명 생성 및 검증을 담당\n */\nexport class SecurityManager {\n private config: SecurityConfig;\n\n constructor(webhookConfig: WebhookConfig) {\n this.config = {\n algorithm: webhookConfig.algorithm || 'sha256',\n header: webhookConfig.signatureHeader || 'X-Webhook-Signature',\n prefix: webhookConfig.signaturePrefix || 'sha256='\n };\n }\n\n /**\n * Webhook 페이로드에 대한 서명 생성\n */\n generateSignature(payload: string, secret: string): string {\n const hmac = crypto.createHmac(this.config.algorithm, secret);\n hmac.update(payload, 'utf8');\n const signature = hmac.digest('hex');\n \n return this.config.prefix ? `${this.config.prefix}${signature}` : signature;\n }\n\n /**\n * Webhook 서명 검증\n */\n verifySignature(payload: string, signature: string, secret: string): boolean {\n try {\n const expectedSignature = this.generateSignature(payload, secret);\n \n // 타이밍 공격 방지를 위한 constant-time 비교\n return this.constantTimeCompare(signature, expectedSignature);\n } catch (error) {\n console.error('Signature verification failed:', error);\n return false;\n }\n }\n\n /**\n * HTTP 헤더에서 서명 추출\n */\n extractSignature(headers: Record<string, string>): string | null {\n const headerName = this.config.header.toLowerCase();\n \n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === headerName) {\n return value;\n }\n }\n \n return null;\n }\n\n /**\n * Webhook 전송을 위한 보안 헤더 생성\n */\n createSecurityHeaders(payload: string, secret: string): Record<string, string> {\n const signature = this.generateSignature(payload, secret);\n const timestamp = Math.floor(Date.now() / 1000).toString();\n \n return {\n [this.config.header]: signature,\n 'X-Webhook-Timestamp': timestamp,\n 'X-Webhook-ID': this.generateWebhookId(),\n 'User-Agent': 'K-Message-Webhook/1.0'\n };\n }\n\n /**\n * 타임스탬프 기반 재생 공격 방지 검증\n */\n verifyTimestamp(timestamp: string, toleranceSeconds: number = 300): boolean {\n try {\n const webhookTime = parseInt(timestamp, 10);\n const currentTime = Math.floor(Date.now() / 1000);\n const timeDiff = Math.abs(currentTime - webhookTime);\n \n return timeDiff <= toleranceSeconds;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Webhook ID 생성 (추적용)\n */\n private generateWebhookId(): string {\n return `wh_${crypto.randomBytes(16).toString('hex')}`;\n }\n\n /**\n * Constant-time 문자열 비교 (타이밍 공격 방지)\n */\n private constantTimeCompare(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n\n return result === 0;\n }\n\n /**\n * 보안 설정 업데이트\n */\n updateConfig(config: Partial<SecurityConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * 현재 보안 설정 반환\n */\n getConfig(): SecurityConfig {\n return { ...this.config };\n }\n}","import type { WebhookConfig } from '../types/webhook.types';\n\nexport interface RetryConfig {\n maxRetries: number;\n baseDelayMs: number;\n maxDelayMs: number;\n backoffMultiplier: number;\n jitter: boolean;\n}\n\nexport interface RetryAttempt {\n attemptNumber: number;\n timestamp: Date;\n success: boolean;\n error?: string;\n nextRetryAt?: Date;\n}\n\n/**\n * Webhook 재시도 관리자\n * 지수 백오프와 지터를 사용한 스마트 재시도 로직\n */\nexport class RetryManager {\n private config: RetryConfig;\n\n constructor(webhookConfig: WebhookConfig) {\n this.config = {\n maxRetries: webhookConfig.maxRetries,\n baseDelayMs: webhookConfig.retryDelayMs,\n maxDelayMs: webhookConfig.maxDelayMs || 300000, // 5분\n backoffMultiplier: webhookConfig.backoffMultiplier || 2,\n jitter: webhookConfig.jitter !== false // 기본값 true\n };\n }\n\n /**\n * 다음 재시도 시간 계산\n */\n calculateNextRetry(attemptNumber: number): Date {\n if (attemptNumber >= this.config.maxRetries) {\n throw new Error(`Maximum retry attempts (${this.config.maxRetries}) exceeded`);\n }\n\n // 지수 백오프 계산\n let delay = this.config.baseDelayMs * Math.pow(this.config.backoffMultiplier, attemptNumber);\n \n // 최대 지연 시간 제한\n delay = Math.min(delay, this.config.maxDelayMs);\n \n // 지터 추가 (랜덤성으로 thundering herd 방지)\n if (this.config.jitter) {\n delay = delay * (0.5 + Math.random() * 0.5);\n }\n\n return new Date(Date.now() + delay);\n }\n\n /**\n * 재시도 가능 여부 확인\n */\n shouldRetry(attemptNumber: number, error?: Error): boolean {\n // 최대 재시도 횟수 확인\n if (attemptNumber >= this.config.maxRetries) {\n return false;\n }\n\n // 에러 타입별 재시도 정책\n if (error) {\n return this.isRetryableError(error);\n }\n\n return true;\n }\n\n /**\n * 재시도 가능한 에러인지 판단\n */\n private isRetryableError(error: Error): boolean {\n const message = error.message.toLowerCase();\n \n // 네트워크 관련 에러들은 재시도 가능\n const retryableErrors = [\n 'timeout',\n 'network',\n 'connection',\n 'econnreset',\n 'enotfound',\n 'econnrefused',\n 'socket hang up'\n ];\n\n return retryableErrors.some(keyword => message.includes(keyword));\n }\n\n /**\n * HTTP 상태 코드별 재시도 정책\n */\n shouldRetryStatus(statusCode: number): boolean {\n // 4xx 에러는 일반적으로 재시도하지 않음 (클라이언트 에러)\n if (statusCode >= 400 && statusCode < 500) {\n // 단, 일부 4xx는 재시도 가능\n const retryable4xx = [408, 429]; // Request Timeout, Too Many Requests\n return retryable4xx.includes(statusCode);\n }\n\n // 5xx 에러는 재시도 가능 (서버 에러)\n if (statusCode >= 500) {\n return true;\n }\n\n // 2xx, 3xx는 성공으로 간주하여 재시도 불필요\n return false;\n }\n\n /**\n * 재시도 통계 계산\n */\n calculateRetryStats(attempts: RetryAttempt[]): {\n totalAttempts: number;\n successfulAttempts: number;\n failedAttempts: number;\n averageDelayMs: number;\n totalTimeMs: number;\n } {\n if (attempts.length === 0) {\n return {\n totalAttempts: 0,\n successfulAttempts: 0,\n failedAttempts: 0,\n averageDelayMs: 0,\n totalTimeMs: 0\n };\n }\n\n const successful = attempts.filter(a => a.success).length;\n const failed = attempts.length - successful;\n \n // 시도 간 평균 지연 시간 계산\n let totalDelay = 0;\n for (let i = 1; i < attempts.length; i++) {\n totalDelay += attempts[i].timestamp.getTime() - attempts[i - 1].timestamp.getTime();\n }\n const averageDelay = attempts.length > 1 ? totalDelay / (attempts.length - 1) : 0;\n\n // 전체 소요 시간\n const totalTime = attempts.length > 0 \n ? attempts[attempts.length - 1].timestamp.getTime() - attempts[0].timestamp.getTime()\n : 0;\n\n return {\n totalAttempts: attempts.length,\n successfulAttempts: successful,\n failedAttempts: failed,\n averageDelayMs: averageDelay,\n totalTimeMs: totalTime\n };\n }\n\n /**\n * 재시도 설정 업데이트\n */\n updateConfig(config: Partial<RetryConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * 현재 재시도 설정 반환\n */\n getConfig(): RetryConfig {\n return { ...this.config };\n }\n\n /**\n * 백오프 지연 시간 계산 (테스트용)\n */\n getBackoffDelay(attemptNumber: number): number {\n let delay = this.config.baseDelayMs * Math.pow(this.config.backoffMultiplier, attemptNumber);\n return Math.min(delay, this.config.maxDelayMs);\n }\n}","import type {\n WebhookConfig,\n WebhookEvent,\n WebhookEndpoint,\n WebhookDelivery,\n WebhookStats,\n WebhookTestResult\n} from '../types/webhook.types';\nimport { WebhookEventType } from '../types/webhook.types';\nimport { WebhookDispatcher } from './webhook.dispatcher';\nimport { WebhookRegistry } from './webhook.registry';\nimport { SecurityManager } from '../security/security.manager';\nimport { RetryManager } from '../retry/retry.manager';\n\nexport class WebhookService {\n private config: WebhookConfig;\n private dispatcher: WebhookDispatcher;\n private registry: WebhookRegistry;\n private securityManager: SecurityManager;\n private retryManager: RetryManager;\n private eventQueue: WebhookEvent[] = [];\n private batchProcessor: NodeJS.Timeout | null = null;\n\n constructor(config: WebhookConfig) {\n this.config = config;\n this.dispatcher = new WebhookDispatcher(config);\n this.registry = new WebhookRegistry();\n this.securityManager = new SecurityManager(config);\n this.retryManager = new RetryManager(config);\n\n this.startBatchProcessor();\n }\n\n /**\n * 웹훅 엔드포인트 등록\n */\n async registerEndpoint(endpoint: Omit<WebhookEndpoint, 'id' | 'createdAt' | 'updatedAt' | 'status'>): Promise<WebhookEndpoint> {\n // URL 유효성 검사\n await this.validateEndpointUrl(endpoint.url);\n\n const newEndpoint: WebhookEndpoint = {\n ...endpoint,\n id: this.generateEndpointId(),\n createdAt: new Date(),\n updatedAt: new Date(),\n status: 'active',\n };\n\n await this.registry.addEndpoint(newEndpoint);\n \n // 테스트 웹훅 전송\n await this.testEndpoint(newEndpoint.id);\n\n return newEndpoint;\n }\n\n /**\n * 웹훅 엔드포인트 수정\n */\n async updateEndpoint(endpointId: string, updates: Partial<WebhookEndpoint>): Promise<WebhookEndpoint> {\n const endpoint = await this.registry.getEndpoint(endpointId);\n if (!endpoint) {\n throw new Error(`Webhook endpoint ${endpointId} not found`);\n }\n\n // URL이 변경된 경우 검증\n if (updates.url && updates.url !== endpoint.url) {\n await this.validateEndpointUrl(updates.url);\n }\n\n const updatedEndpoint = {\n ...endpoint,\n ...updates,\n updatedAt: new Date(),\n };\n\n await this.registry.updateEndpoint(endpointId, updatedEndpoint);\n return updatedEndpoint;\n }\n\n /**\n * 웹훅 엔드포인트 삭제\n */\n async deleteEndpoint(endpointId: string): Promise<void> {\n await this.registry.removeEndpoint(endpointId);\n }\n\n /**\n * 웹훅 엔드포인트 조회\n */\n async getEndpoint(endpointId: string): Promise<WebhookEndpoint | null> {\n return this.registry.getEndpoint(endpointId);\n }\n\n /**\n * 모든 웹훅 엔드포인트 조회\n */\n async listEndpoints(): Promise<WebhookEndpoint[]> {\n return this.registry.listEndpoints();\n }\n\n /**\n * 이벤트 발생 (비동기 처리)\n */\n async emit(event: WebhookEvent): Promise<void> {\n // 활성화된 이벤트 타입인지 확인\n if (!this.config.enabledEvents.includes(event.type)) {\n return;\n }\n\n // 이벤트 검증\n this.validateEvent(event);\n\n // 큐에 추가\n this.eventQueue.push(event);\n\n // 배치 크기에 도달한 경우 즉시 처리\n if (this.eventQueue.length >= this.config.batchSize) {\n await this.processBatch();\n }\n }\n\n /**\n * 이벤트 발생 (동기 처리)\n */\n async emitSync(event: WebhookEvent): Promise<WebhookDelivery[]> {\n this.validateEvent(event);\n\n const endpoints = await this.getMatchingEndpoints(event);\n const deliveries: WebhookDelivery[] = [];\n\n for (const endpoint of endpoints) {\n const delivery = await this.dispatcher.dispatch(event, endpoint);\n deliveries.push(delivery);\n }\n\n return deliveries;\n }\n\n /**\n * 웹훅 엔드포인트 테스트\n */\n async testEndpoint(endpointId: string): Promise<WebhookTestResult> {\n const endpoint = await this.registry.getEndpoint(endpointId);\n if (!endpoint) {\n throw new Error(`Webhook endpoint ${endpointId} not found`);\n }\n\n const testEvent: WebhookEvent = {\n id: `test_${Date.now()}`,\n type: WebhookEventType.SYSTEM_MAINTENANCE, // 테스트용 이벤트\n timestamp: new Date(),\n data: {\n test: true,\n message: 'This is a test webhook',\n },\n metadata: {\n correlationId: `test_${endpointId}`,\n },\n version: '1.0',\n };\n\n const startTime = Date.now();\n \n try {\n const delivery = await this.dispatcher.dispatch(testEvent, endpoint);\n const endTime = Date.now();\n\n return {\n endpointId,\n url: endpoint.url,\n success: delivery.status === 'success',\n httpStatus: delivery.attempts[0]?.httpStatus,\n responseTime: endTime - startTime,\n testedAt: new Date(),\n };\n } catch (error) {\n const endTime = Date.now();\n \n return {\n endpointId,\n url: endpoint.url,\n success: false,\n responseTime: endTime - startTime,\n error: error instanceof Error ? error.message : 'Unknown error',\n testedAt: new Date(),\n };\n }\n }\n\n /**\n * 웹훅 통계 조회\n */\n async getStats(endpointId: string, timeRange: { start: Date; end: Date }): Promise<WebhookStats> {\n const deliveries = await this.registry.getDeliveries(endpointId, timeRange);\n \n const successful = deliveries.filter(d => d.status === 'success');\n const failed = deliveries.filter(d => d.status === 'failed' || d.status === 'exhausted');\n \n const totalLatency = deliveries.reduce((sum, d) => {\n const lastAttempt = d.attempts[d.attempts.length - 1];\n return sum + (lastAttempt?.latencyMs || 0);\n }, 0);\n\n const eventBreakdown: Record<WebhookEventType, number> = {} as any;\n const errorBreakdown: Record<string, number> = {};\n\n for (const delivery of deliveries) {\n // 이벤트 유형별 집계는 실제 구현에서 이벤트 정보를 조회해야 함\n \n // 에러 유형별 집계\n if (delivery.status === 'failed' || delivery.status === 'exhausted') {\n const lastAttempt = delivery.attempts[delivery.attempts.length - 1];\n const errorKey = lastAttempt?.error || `HTTP ${lastAttempt?.httpStatus || 'Unknown'}`;\n errorBreakdown[errorKey] = (errorBreakdown[errorKey] || 0) + 1;\n }\n }\n\n return {\n endpointId,\n timeRange,\n totalDeliveries: deliveries.length,\n successfulDeliveries: successful.length,\n failedDeliveries: failed.length,\n averageLatencyMs: deliveries.length > 0 ? totalLatency / deliveries.length : 0,\n successRate: deliveries.length > 0 ? (successful.length / deliveries.length) * 100 : 0,\n eventBreakdown,\n errorBreakdown,\n };\n }\n\n /**\n * 실패한 웹훅 재시도\n */\n async retryFailed(endpointId?: string, eventType?: WebhookEventType): Promise<number> {\n const failedDeliveries = await this.registry.getFailedDeliveries(endpointId, eventType);\n let retriedCount = 0;\n\n for (const delivery of failedDeliveries) {\n const attemptCount = delivery.attempts.length;\n if (this.retryManager.shouldRetry(attemptCount)) {\n // 재시도 스케줄링 (실제 구현에서는 큐 시스템 사용)\n setTimeout(async () => {\n const endpoint = await this.registry.getEndpoint(delivery.endpointId);\n if (endpoint) {\n await this.dispatcher.dispatch(\n JSON.parse(delivery.payload), \n endpoint\n );\n }\n }, this.retryManager.getBackoffDelay(attemptCount));\n retriedCount++;\n }\n }\n\n return retriedCount;\n }\n\n /**\n * 웹훅 일시 중단\n */\n async pauseEndpoint(endpointId: string): Promise<void> {\n await this.updateEndpoint(endpointId, { status: 'suspended' });\n }\n\n /**\n * 웹훅 재개\n */\n async resumeEndpoint(endpointId: string): Promise<void> {\n await this.updateEndpoint(endpointId, { status: 'active' });\n }\n\n /**\n * 웹훅 전달 내역 조회\n */\n async getDeliveries(\n endpointId?: string,\n eventType?: WebhookEventType,\n status?: string,\n limit = 100\n ): Promise<WebhookDelivery[]> {\n return this.registry.getDeliveries(endpointId, undefined, eventType, status, limit);\n }\n\n private async processBatch(): Promise<void> {\n if (this.eventQueue.length === 0) {\n return;\n }\n\n const batch = this.eventQueue.splice(0, this.config.batchSize);\n \n try {\n for (const event of batch) {\n const endpoints = await this.getMatchingEndpoints(event);\n \n for (const endpoint of endpoints) {\n // 비동기로 전달 (에러가 발생해도 다른 엔드포인트에 영향 없음)\n this.dispatcher.dispatch(event, endpoint).catch(error => {\n console.error(`Failed to dispatch webhook to ${endpoint.url}:`, error);\n });\n }\n }\n } catch (error) {\n console.error('Batch processing failed:', error);\n // 실패한 이벤트를 다시 큐에 추가 (재시도)\n this.eventQueue.unshift(...batch);\n }\n }\n\n private async getMatchingEndpoints(event: WebhookEvent): Promise<WebhookEndpoint[]> {\n const allEndpoints = await this.registry.listEndpoints();\n \n return allEndpoints.filter(endpoint => {\n // 비활성 엔드포인트 제외\n if (endpoint.status !== 'active') {\n return false;\n }\n\n // 이벤트 타입 필터\n if (!endpoint.events.includes(event.type)) {\n return false;\n }\n\n // 추가 필터 적용\n if (endpoint.filters) {\n if (endpoint.filters.providerId && event.metadata.providerId) {\n if (!endpoint.filters.providerId.includes(event.metadata.providerId)) {\n return false;\n }\n }\n\n if (endpoint.filters.channelId && event.metadata.channelId) {\n if (!endpoint.filters.channelId.includes(event.metadata.channelId)) {\n return false;\n }\n }\n\n if (endpoint.filters.templateId && event.metadata.templateId) {\n if (!endpoint.filters.templateId.includes(event.metadata.templateId)) {\n return false;\n }\n }\n }\n\n return true;\n });\n }\n\n private validateEvent(event: WebhookEvent): void {\n if (!event.id) {\n throw new Error('Event ID is required');\n }\n\n if (!event.type) {\n throw new Error('Event type is required');\n }\n\n if (!event.timestamp) {\n throw new Error('Event timestamp is required');\n }\n\n if (!event.version) {\n throw new Error('Event version is required');\n }\n }\n\n private async validateEndpointUrl(url: string): Promise<void> {\n try {\n const parsedUrl = new URL(url);\n \n // HTTPS 필수 (개발 환경 제외)\n if (parsedUrl.protocol !== 'https:' && !url.includes('localhost') && !url.includes('127.0.0.1')) {\n throw new Error('Webhook URL must use HTTPS');\n }\n\n // 로컬호스트 및 프라이빗 IP 차단 (프로덕션에서)\n if (process.env.NODE_ENV === 'production') {\n const hostname = parsedUrl.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.')) {\n throw new Error('Private IP addresses are not allowed in production');\n }\n }\n\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error('Invalid webhook URL');\n }\n }\n\n private generateEndpointId(): string {\n return `webhook_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n }\n\n private startBatchProcessor(): void {\n this.batchProcessor = setInterval(async () => {\n try {\n await this.processBatch();\n } catch (error) {\n console.error('Batch processor error:', error);\n }\n }, this.config.batchTimeoutMs);\n }\n\n /**\n * 서비스 종료 시 정리\n */\n async shutdown(): Promise<void> {\n if (this.batchProcessor) {\n clearInterval(this.batchProcessor);\n this.batchProcessor = null;\n }\n\n // 남은 이벤트 처리\n if (this.eventQueue.length > 0) {\n await this.processBatch();\n }\n\n await this.dispatcher.shutdown();\n // RetryManager는 상태가 없으므로 정리 불필요\n }\n}","/**\n * Batch Webhook Dispatcher\n * 대량 웹훅 요청을 효율적으로 처리하는 배치 디스패처\n */\n\nimport type { \n WebhookEvent, \n WebhookEndpoint, \n WebhookDelivery,\n WebhookBatch \n} from '../types/webhook.types';\nimport type { BatchConfig, DispatchJob } from './types';\nimport { EventEmitter } from 'events';\n\nexport class BatchDispatcher extends EventEmitter {\n private config: BatchConfig;\n private pendingJobs: Map<string, DispatchJob[]> = new Map(); // endpointId -> jobs\n private activeBatches: Map<string, WebhookBatch> = new Map();\n private batchProcessor: NodeJS.Timeout | null = null;\n\n private defaultConfig: BatchConfig = {\n maxBatchSize: 100,\n batchTimeoutMs: 5000,\n maxConcurrentBatches: 10,\n enablePrioritization: true,\n priorityLevels: 3\n };\n\n constructor(config: Partial<BatchConfig> = {}) {\n super();\n this.config = { ...this.defaultConfig, ...config };\n this.startBatchProcessor();\n }\n\n /**\n * 배치 작업 추가\n */\n async addJob(job: DispatchJob): Promise<void> {\n const endpointId = job.endpoint.id;\n \n if (!this.pendingJobs.has(endpointId)) {\n this.pendingJobs.set(endpointId, []);\n }\n \n const jobs = this.pendingJobs.get(endpointId)!;\n \n // 우선순위 기반 삽입\n if (this.config.enablePrioritization) {\n this.insertJobByPriority(jobs, job);\n } else {\n jobs.push(job);\n }\n\n // 배치 크기에 도달한 경우 즉시 처리\n if (jobs.length >= this.config.maxBatchSize) {\n await this.processBatchForEndpoint(endpointId);\n }\n\n this.emit('jobAdded', { endpointId, jobId: job.id, queueSize: jobs.length });\n }\n\n /**\n * 특정 엔드포인트의 배치 처리\n */\n async processBatchForEndpoint(endpointId: string): Promise<WebhookBatch | null> {\n const jobs = this.pendingJobs.get(endpointId);\n if (!jobs || jobs.length === 0) {\n return null;\n }\n\n // 동시 실행 중인 배치 수 확인\n if (this.activeBatches.size >= this.config.maxConcurrentBatches) {\n this.emit('batchSkipped', { endpointId, reason: 'max_concurrent_batches' });\n return null;\n }\n\n // 배치 생성\n const batchJobs = jobs.splice(0, this.config.maxBatchSize);\n const batch = this.createBatch(endpointId, batchJobs);\n \n this.activeBatches.set(batch.id, batch);\n \n try {\n this.emit('batchStarted', { batchId: batch.id, endpointId, jobCount: batchJobs.length });\n \n // 배치 실행\n await this.executeBatch(batch, batchJobs);\n \n batch.status = 'completed';\n this.emit('batchCompleted', { batchId: batch.id, endpointId, success: true });\n \n } catch (error) {\n batch.status = 'failed';\n this.emit('batchFailed', { batchId: batch.id, endpointId, error: error instanceof Error ? error.message : 'Unknown error' });\n \n // 실패한 작업을 다시 큐에 추가 (재시도를 위해)\n this.requeueFailedJobs(batchJobs);\n } finally {\n this.activeBatches.delete(batch.id);\n }\n\n return batch;\n }\n\n /**\n * 모든 대기 중인 배치 처리\n */\n async processAllBatches(): Promise<WebhookBatch[]> {\n const processedBatches: WebhookBatch[] = [];\n const endpointIds = Array.from(this.pendingJobs.keys());\n \n for (const endpointId of endpointIds) {\n const batch = await this.processBatchForEndpoint(endpointId);\n if (batch) {\n processedBatches.push(batch);\n }\n }\n \n return processedBatches;\n }\n\n /**\n * 배치 통계 조회\n */\n getBatchStats(): {\n pendingJobsCount: number;\n activeBatchesCount: number;\n endpointsWithPendingJobs: number;\n averageQueueSize: number;\n } {\n const endpointIds = Array.from(this.pendingJobs.keys());\n const totalPendingJobs = endpointIds.reduce((sum, id) => {\n return sum + (this.pendingJobs.get(id)?.length || 0);\n }, 0);\n \n return {\n pendingJobsCount: totalPendingJobs,\n activeBatchesCount: this.activeBatches.size,\n endpointsWithPendingJobs: endpointIds.length,\n averageQueueSize: endpointIds.length > 0 ? totalPendingJobs / endpointIds.length : 0\n };\n }\n\n /**\n * 특정 엔드포인트의 대기 중인 작업 수 조회\n */\n getPendingJobCount(endpointId: string): number {\n return this.pendingJobs.get(endpointId)?.length || 0;\n }\n\n /**\n * 배치 처리기 시작\n */\n private startBatchProcessor(): void {\n this.batchProcessor = setInterval(async () => {\n try {\n await this.processAllBatches();\n } catch (error) {\n this.emit('processorError', error);\n }\n }, this.config.batchTimeoutMs);\n }\n\n /**\n * 우선순위 기반 작업 삽입\n */\n private insertJobByPriority(jobs: DispatchJob[], newJob: DispatchJob): void {\n let insertIndex = 0;\n \n for (let i = 0; i < jobs.length; i++) {\n if (jobs[i].priority <= newJob.priority) {\n insertIndex = i;\n break;\n }\n insertIndex = i + 1;\n }\n \n jobs.splice(insertIndex, 0, newJob);\n }\n\n /**\n * 배치 생성\n */\n private createBatch(endpointId: string, jobs: DispatchJob[]): WebhookBatch {\n return {\n id: `batch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n endpointId,\n events: jobs.map(job => job.event),\n createdAt: new Date(),\n scheduledAt: new Date(),\n status: 'processing'\n };\n }\n\n /**\n * 배치 실행\n */\n private async executeBatch(batch: WebhookBatch, jobs: DispatchJob[]): Promise<void> {\n const endpoint = jobs[0]?.endpoint;\n if (!endpoint) {\n throw new Error('No endpoint found for batch');\n }\n\n // 병렬로 모든 작업 실행\n const deliveryPromises = jobs.map(job => this.executeJob(job));\n \n try {\n const deliveries = await Promise.allSettled(deliveryPromises);\n \n // 결과 분석\n const successful = deliveries.filter(result => result.status === 'fulfilled').length;\n const failed = deliveries.length - successful;\n \n this.emit('batchExecuted', {\n batchId: batch.id,\n endpointId: batch.endpointId,\n total: deliveries.length,\n successful,\n failed\n });\n \n if (failed > 0) {\n // 일부 실패가 있으면 에러로 처리\n throw new Error(`Batch partially failed: ${failed}/${deliveries.length} jobs failed`);\n }\n \n } catch (error) {\n this.emit('batchExecutionError', {\n batchId: batch.id,\n endpointId: batch.endpointId,\n error: error instanceof Error ? error.message : 'Unknown error'\n });\n throw error;\n }\n }\n\n /**\n * 개별 작업 실행 (실제로는 WebhookDispatcher 사용)\n */\n private async executeJob(job: DispatchJob): Promise<WebhookDelivery> {\n // 이 메서드는 실제 구현에서는 WebhookDispatcher를 사용해야 함\n // 여기서는 모킹된 구현\n \n const delivery: WebhookDelivery = {\n id: `delivery_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n endpointId: job.endpoint.id,\n eventId: job.event.id,\n url: job.endpoint.url,\n httpMethod: 'POST',\n headers: { 'Content-Type': 'application/json' },\n payload: JSON.stringify(job.event),\n attempts: [],\n status: 'pending',\n createdAt: new Date()\n };\n\n // 시뮬레이션: 90% 성공률\n const success = Math.random() > 0.1;\n \n delivery.attempts.push({\n attemptNumber: 1,\n timestamp: new Date(),\n httpStatus: success ? 200 : 500,\n responseBody: success ? 'OK' : 'Internal Server Error',\n error: success ? undefined : 'Server error',\n latencyMs: Math.floor(Math.random() * 1000) + 100\n });\n \n delivery.status = success ? 'success' : 'failed';\n delivery.completedAt = new Date();\n\n return delivery;\n }\n\n /**\n * 실패한 작업들을 다시 큐에 추가\n */\n private requeueFailedJobs(jobs: DispatchJob[]): void {\n for (const job of jobs) {\n job.attempts++;\n if (job.attempts < job.maxAttempts) {\n // 재시도 지연 시간 계산\n const baseDelay = 1000; // 1초\n const backoffMultiplier = 2;\n const delay = baseDelay * Math.pow(backoffMultiplier, job.attempts - 1);\n \n job.nextRetryAt = new Date(Date.now() + delay);\n job.scheduledAt = job.nextRetryAt;\n\n // 다시 큐에 추가\n setTimeout(() => {\n this.addJob(job).catch(error => {\n this.emit('requeueError', { jobId: job.id, error: error instanceof Error ? error.message : 'Unknown error' });\n });\n }, delay);\n } else {\n this.emit('jobExhausted', { jobId: job.id, endpointId: job.endpoint.id, attempts: job.attempts });\n }\n }\n }\n\n /**\n * 배치 처리기 정지\n */\n async shutdown(): Promise<void> {\n if (this.batchProcessor) {\n clearInterval(this.batchProcessor);\n this.batchProcessor = null;\n }\n\n // 활성 배치들이 완료될 때까지 대기\n const maxWaitTime = 30000; // 30초\n const startTime = Date.now();\n \n while (this.activeBatches.size > 0 && (Date.now() - startTime) < maxWaitTime) {\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n\n this.emit('shutdown', { \n pendingJobs: this.getBatchStats().pendingJobsCount,\n activeBatches: this.activeBatches.size \n });\n }\n}","/**\n * Queue Manager\n * 웹훅 작업 큐 관리 시스템\n */\n\nimport type { QueueConfig, DispatchJob } from './types';\nimport { EventEmitter } from 'events';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport class QueueManager extends EventEmitter {\n private config: QueueConfig;\n private queues: Map<string, DispatchJob[]> = new Map(); // priority level -> jobs\n private highPriorityQueue: DispatchJob[] = [];\n private mediumPriorityQueue: DispatchJob[] = [];\n private lowPriorityQueue: DispatchJob[] = [];\n private delayedJobs: Map<string, NodeJS.Timeout> = new Map();\n private totalJobs = 0;\n\n private defaultConfig: QueueConfig = {\n maxQueueSize: 10000,\n persistToDisk: false,\n compressionEnabled: false,\n ttlMs: 24 * 60 * 60 * 1000 // 24시간\n };\n\n constructor(config: Partial<QueueConfig> = {}) {\n super();\n this.config = { ...this.defaultConfig, ...config };\n \n // 우선순위 큐 초기화\n this.queues.set('high', this.highPriorityQueue);\n this.queues.set('medium', this.mediumPriorityQueue);\n this.queues.set('low', this.lowPriorityQueue);\n\n if (this.config.persistToDisk && this.config.diskPath) {\n this.loadFromDisk().catch(error => {\n this.emit('diskLoadError', error);\n });\n }\n\n // TTL 정리 작업 시작\n this.startTTLCleanup();\n }\n\n /**\n * 작업을 큐에 추가\n */\n async enqueue(job: DispatchJob): Promise<boolean> {\n // 큐 크기 제한 확인\n if (this.totalJobs >= this.config.maxQueueSize) {\n this.emit('queueFull', { totalJobs: this.totalJobs, maxSize: this.config.maxQueueSize });\n return false;\n }\n\n // 스케줄된 시간이 미래인 경우 지연 처리\n if (job.scheduledAt > new Date()) {\n await this.scheduleDelayedJob(job);\n return true;\n }\n\n // 우선순위에 따른 큐 선택\n const queueName = this.getQueueName(job.priority);\n const queue = this.queues.get(queueName);\n \n if (!queue) {\n throw new Error(`Invalid queue name: ${queueName}`);\n }\n\n // 큐에 추가\n queue.push(job);\n this.totalJobs++;\n\n this.emit('jobEnqueued', {\n jobId: job.id,\n priority: job.priority,\n queueName,\n totalJobs: this.totalJobs\n });\n\n // 디스크 영속화\n if (this.config.persistToDisk) {\n await this.saveToDisk().catch(error => {\n this.emit('diskSaveError', error);\n });\n }\n\n return true;\n }\n\n /**\n * 우선순위에 따라 작업 추출\n */\n async dequeue(): Promise<DispatchJob | null> {\n // 높은 우선순위부터 확인\n for (const [queueName, queue] of this.queues.entries()) {\n if (queue.length > 0) {\n const job = queue.shift()!;\n this.totalJobs--;\n\n this.emit('jobDequeued', {\n jobId: job.id,\n queueName,\n totalJobs: this.totalJobs\n });\n\n // 디스크 영속화\n if (this.config.persistToDisk) {\n await this.saveToDisk().catch(error => {\n this.emit('diskSaveError', error);\n });\n }\n\n return job;\n }\n }\n\n return null;\n }\n\n /**\n * 특정 우선순위 큐에서 작업 추출\n */\n async dequeueFromPriority(priority: number): Promise<DispatchJob | null> {\n const queueName = this.getQueueName(priority);\n const queue = this.queues.get(queueName);\n \n if (!queue || queue.length === 0) {\n return null;\n }\n\n const job = queue.shift()!;\n this.totalJobs--;\n\n this.emit('jobDequeued', {\n jobId: job.id,\n queueName,\n totalJobs: this.totalJobs\n });\n\n return job;\n }\n\n /**\n * 작업 상태 확인\n */\n peek(): DispatchJob | null {\n for (const queue of this.queues.values()) {\n if (queue.length > 0) {\n return queue[0];\n }\n }\n return null;\n }\n\n /**\n * 특정 작업 제거\n */\n async removeJob(jobId: string): Promise<boolean> {\n for (const [queueName, queue] of this.queues.entries()) {\n const index = queue.findIndex(job => job.id === jobId);\n if (index !== -1) {\n queue.splice(index, 1);\n this.totalJobs--;\n\n this.emit('jobRemoved', {\n jobId,\n queueName,\n totalJobs: this.totalJobs\n });\n\n // 디스크 영속화\n if (this.config.persistToDisk) {\n await this.saveToDisk().catch(error => {\n this.emit('diskSaveError', error);\n });\n }\n\n return true;\n }\n }\n\n // 지연된 작업에서도 확인\n const delayedTimeout = this.delayedJobs.get(jobId);\n if (delayedTimeout) {\n clearTimeout(delayedTimeout);\n this.delayedJobs.delete(jobId);\n this.emit('delayedJobCanceled', { jobId });\n return true;\n }\n\n return false;\n }\n\n /**\n * 큐 통계 조회\n */\n getStats(): {\n totalJobs: number;\n highPriorityJobs: number;\n mediumPriorityJobs: number;\n lowPriorityJobs: number;\n delayedJobs: number;\n queueUtilization: number;\n } {\n return {\n totalJobs: this.totalJobs,\n highPriorityJobs: this.highPriorityQueue.length,\n mediumPriorityJobs: this.mediumPriorityQueue.length,\n lowPriorityJobs: this.lowPriorityQueue.length,\n delayedJobs: this.delayedJobs.size,\n queueUtilization: (this.totalJobs / this.config.maxQueueSize) * 100\n };\n }\n\n /**\n * 큐 비우기\n */\n async clear(): Promise<void> {\n for (const queue of this.queues.values()) {\n queue.length = 0;\n }\n \n // 지연된 작업들 취소\n for (const timeout of this.delayedJobs.values()) {\n clearTimeout(timeout);\n }\n this.delayedJobs.clear();\n \n this.totalJobs = 0;\n\n this.emit('queueCleared');\n\n // 디스크에서도 삭제\n if (this.config.persistToDisk) {\n await this.saveToDisk().catch(error => {\n this.emit('diskSaveError', error);\n });\n }\n }\n\n /**\n * 만료된 작업 정리\n */\n async cleanupExpiredJobs(): Promise<number> {\n const now = new Date();\n let removedCount = 0;\n\n for (const [queueName, queue] of this.queues.entries()) {\n const initialLength = queue.length;\n \n // TTL 확인하여 만료된 작업 제거\n for (let i = queue.length - 1; i >= 0; i--) {\n const job = queue[i];\n const age = now.getTime() - job.createdAt.getTime();\n \n if (age > this.config.ttlMs) {\n queue.splice(i, 1);\n this.totalJobs--;\n removedCount++;\n \n this.emit('jobExpired', {\n jobId: job.id,\n queueName,\n age\n });\n }\n }\n }\n\n if (removedCount > 0) {\n this.emit('expiredJobsCleanup', { removedCount, totalJobs: this.totalJobs });\n \n // 디스크 영속화\n if (this.config.persistToDisk) {\n await this.saveToDisk().catch(error => {\n this.emit('diskSaveError', error);\n });\n }\n }\n\n return removedCount;\n }\n\n /**\n * 우선순위 숫자를 큐 이름으로 변환\n */\n private getQueueName(priority: number): string {\n if (priority >= 8) return 'high';\n if (priority >= 5) return 'medium';\n return 'low';\n }\n\n /**\n * 지연된 작업 스케줄링\n */\n private async scheduleDelayedJob(job: DispatchJob): Promise<void> {\n const delay = job.scheduledAt.getTime() - Date.now();\n \n const timeout = setTimeout(async () => {\n this.delayedJobs.delete(job.id);\n \n // 정시가 되면 큐에 추가\n const success = await this.enqueue({\n ...job,\n scheduledAt: new Date() // 즉시 처리 가능하도록 변경\n });\n \n if (success) {\n this.emit('delayedJobActivated', { jobId: job.id });\n }\n }, delay);\n\n this.delayedJobs.set(job.id, timeout);\n \n this.emit('jobScheduled', {\n jobId: job.id,\n scheduledAt: job.scheduledAt,\n delay\n });\n }\n\n /**\n * TTL 정리 작업 시작\n */\n private startTTLCleanup(): void {\n // 5분마다 만료된 작업 정리\n setInterval(async () => {\n try {\n await this.cleanupExpiredJobs();\n } catch (error) {\n this.emit('cleanupError', error);\n }\n }, 5 * 60 * 1000);\n }\n\n /**\n * 디스크에 큐 상태 저장\n */\n private async saveToDisk(): Promise<void> {\n if (!this.config.diskPath) return;\n\n try {\n const data = {\n queues: {\n high: this.highPriorityQueue,\n medium: this.mediumPriorityQueue,\n low: this.lowPriorityQueue\n },\n totalJobs: this.totalJobs,\n timestamp: new Date().toISOString()\n };\n\n const json = JSON.stringify(data, null, 2);\n const filePath = path.join(this.config.diskPath, 'webhook-queue.json');\n \n // 디렉토리 생성\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n \n // 파일 저장\n await fs.writeFile(filePath, json, 'utf8');\n \n this.emit('diskSaved', { filePath, totalJobs: this.totalJobs });\n \n } catch (error) {\n this.emit('diskSaveError', error);\n throw error;\n }\n }\n\n /**\n * 디스크에서 큐 상태 로드\n */\n private async loadFromDisk(): Promise<void> {\n if (!this.config.diskPath) return;\n\n try {\n const filePath = path.join(this.config.diskPath, 'webhook-queue.json');\n const json = await fs.readFile(filePath, 'utf8');\n const data = JSON.parse(json);\n\n // 큐 복원\n this.highPriorityQueue.length = 0;\n this.mediumPriorityQueue.length = 0;\n this.lowPriorityQueue.length = 0;\n\n this.highPriorityQueue.push(...(data.queues.high || []));\n this.mediumPriorityQueue.push(...(data.queues.medium || []));\n this.lowPriorityQueue.push(...(data.queues.low || []));\n\n this.totalJobs = data.totalJobs || 0;\n\n this.emit('diskLoaded', { \n filePath, \n totalJobs: this.totalJobs,\n timestamp: data.timestamp \n });\n\n } catch (error) {\n if ((error as any).code !== 'ENOENT') {\n this.emit('diskLoadError', error);\n }\n // 파일이 없는 경우는 정상 (처음 시작)\n }\n }\n\n /**\n * 큐 관리자 종료\n */\n async shutdown(): Promise<void> {\n // 지연된 작업들 취소\n for (const timeout of this.delayedJobs.values()) {\n clearTimeout(timeout);\n }\n this.delayedJobs.clear();\n\n // 마지막 디스크 저장\n if (this.config.persistToDisk) {\n await this.saveToDisk().catch(error => {\n this.emit('diskSaveError', error);\n });\n }\n\n this.emit('shutdown', { totalJobs: this.totalJobs });\n }\n}","/**\n * Load Balancer\n * 웹훅 엔드포인트 간의 부하 분산 관리\n */\n\nimport type { WebhookEndpoint } from '../types/webhook.types';\nimport type { LoadBalancerConfig, CircuitBreakerState } from './types';\nimport { EventEmitter } from 'events';\n\ninterface EndpointHealth {\n endpointId: string;\n isHealthy: boolean;\n consecutiveFailures: number;\n lastHealthCheckAt: Date;\n averageResponseTime: number;\n activeConnections: number;\n}\n\nexport class LoadBalancer extends EventEmitter {\n private config: LoadBalancerConfig;\n private endpointHealth: Map<string, EndpointHealth> = new Map();\n private circuitBreakers: Map<string, CircuitBreakerState> = new Map();\n private connectionCounts: Map<string, number> = new Map();\n private roundRobinIndex = 0;\n private healthCheckInterval: NodeJS.Timeout | null = null;\n\n private defaultConfig: LoadBalancerConfig = {\n strategy: 'round-robin',\n healthCheckInterval: 30000, // 30초\n healthCheckTimeoutMs: 5000,\n weights: {}\n };\n\n constructor(config: Partial<LoadBalancerConfig> = {}) {\n super();\n this.config = { ...this.defaultConfig, ...config };\n \n this.startHealthChecks();\n }\n\n /**\n * 엔드포인트 등록\n */\n async registerEndpoint(endpoint: WebhookEndpoint): Promise<void> {\n const health: EndpointHealth = {\n endpointId: endpoint.id,\n isHealthy: true,\n consecutiveFailures: 0,\n lastHealthCheckAt: new Date(),\n averageResponseTime: 0,\n activeConnections: 0\n };\n\n this.endpointHealth.set(endpoint.id, health);\n this.connectionCounts.set(endpoint.id, 0);\n \n // 초기 건강 상태 확인\n await this.checkEndpointHealth(endpoint);\n\n this.emit('endpointRegistered', { endpointId: endpoint.id, isHealthy: health.isHealthy });\n }\n\n /**\n * 엔드포인트 등록 해제\n */\n async unregisterEndpoint(endpointId: string): Promise<void> {\n this.endpointHealth.delete(endpointId);\n this.circuitBreakers.delete(endpointId);\n this.connectionCounts.delete(endpointId);\n\n this.emit('endpointUnregistered', { endpointId });\n }\n\n /**\n * 로드 밸런싱을 통한 엔드포인트 선택\n */\n async selectEndpoint(endpoints: WebhookEndpoint[]): Promise<WebhookEndpoint | null> {\n const healthyEndpoints = endpoints.filter(endpoint => {\n const health = this.endpointHealth.get(endpoint.id);\n const circuitBreaker = this.circuitBreakers.get(endpoint.id);\n \n return health?.isHealthy && \n endpoint.status === 'active' &&\n circuitBreaker?.state !== 'open';\n });\n\n if (healthyEndpoints.length === 0) {\n // 모든 엔드포인트가 비정상인 경우 Circuit Breaker half-open 시도\n const halfOpenEndpoint = this.tryHalfOpenEndpoint(endpoints);\n if (halfOpenEndpoint) {\n return halfOpenEndpoint;\n }\n \n this.emit('noHealthyEndpoints', { totalEndpoints: endpoints.length });\n return null;\n }\n\n let selectedEndpoint: WebhookEndpoint;\n\n switch (this.config.strategy) {\n case 'round-robin':\n selectedEndpoint = this.selectRoundRobin(healthyEndpoints);\n break;\n \n case 'least-connections':\n selectedEndpoint = this.selectLeastConnections(healthyEndpoints);\n break;\n \n case 'weighted':\n selectedEndpoint = this.selectWeighted(healthyEndpoints);\n break;\n \n case 'random':\n selectedEndpoint = this.selectRandom(healthyEndpoints);\n break;\n \n default:\n selectedEndpoint = healthyEndpoints[0];\n }\n\n // 연결 수 증가\n this.incrementConnections(selectedEndpoint.id);\n\n this.emit('endpointSelected', {\n endpointId: selectedEndpoint.id,\n strategy: this.config.strategy,\n availableEndpoints: healthyEndpoints.length\n });\n\n return selectedEndpoint;\n }\n\n /**\n * 요청 완료 시 호출 (연결 수 감소 및 통계 업데이트)\n */\n async onRequestComplete(endpointId: string, success: boolean, responseTime: number): Promise<void> {\n // 연결 수 감소\n this.decrementConnections(endpointId);\n\n // 건강 상태 업데이트\n const health = this.endpointHealth.get(endpointId);\n if (health) {\n // 평균 응답 시간 업데이트\n if (health.averageResponseTime === 0) {\n health.averageResponseTime = responseTime;\n } else {\n health.averageResponseTime = (health.averageResponseTime * 0.8) + (responseTime * 0.2);\n }\n\n if (success) {\n health.consecutiveFailures = 0;\n health.isHealthy = true;\n \n // Circuit Breaker 복구\n const circuitBreaker = this.circuitBreakers.get(endpointId);\n if (circuitBreaker) {\n if (circuitBreaker.state === 'half-open') {\n circuitBreaker.state = 'closed';\n circuitBreaker.failureCount = 0;\n this.emit('circuitBreakerClosed', { endpointId });\n }\n }\n } else {\n health.consecutiveFailures++;\n \n // 3번 연속 실패 시 비정상으로 마킹\n if (health.consecutiveFailures >= 3) {\n health.isHealthy = false;\n this.emit('endpointUnhealthy', { endpointId, consecutiveFailures: health.consecutiveFailures });\n }\n\n // Circuit Breaker 업데이트\n this.updateCircuitBreaker(endpointId, false);\n }\n }\n\n this.emit('requestCompleted', {\n endpointId,\n success,\n responseTime,\n averageResponseTime: health?.averageResponseTime\n });\n }\n\n /**\n * 엔드포인트 건강 상태 조회\n */\n getEndpointHealth(endpointId: string): EndpointHealth | null {\n return this.endpointHealth.get(endpointId) || null;\n }\n\n /**\n * 모든 엔드포인트 건강 상태 조회\n */\n getAllEndpointHealth(): EndpointHealth[] {\n return Array.from(this.endpointHealth.values());\n }\n\n /**\n * 로드 밸런서 통계 조회\n */\n getStats(): {\n totalEndpoints: number;\n healthyEndpoints: number;\n activeConnections: number;\n circuitBreakersOpen: number;\n averageResponseTime: number;\n } {\n const healths = Array.from(this.endpointHealth.values());\n const totalConnections = Array.from(this.connectionCounts.values()).reduce((sum, count) => sum + count, 0);\n const circuitBreakersOpen = Array.from(this.circuitBreakers.values()).filter(cb => cb.state === 'open').length;\n const avgResponseTime = healths.length > 0 \n ? healths.reduce((sum, h) => sum + h.averageResponseTime, 0) / healths.length \n : 0;\n\n return {\n totalEndpoints: healths.length,\n healthyEndpoints: healths.filter(h => h.isHealthy).length,\n activeConnections: totalConnections,\n circuitBreakersOpen,\n averageResponseTime: avgResponseTime\n };\n }\n\n /**\n * Round Robin 전략\n */\n private selectRoundRobin(endpoints: WebhookEndpoint[]): WebhookEndpoint {\n const endpoint = endpoints[this.roundRobinIndex % endpoints.length];\n this.roundRobinIndex = (this.roundRobinIndex + 1) % endpoints.length;\n return endpoint;\n }\n\n /**\n * Least Connections 전략\n */\n private selectLeastConnections(endpoints: WebhookEndpoint[]): WebhookEndpoint {\n return endpoints.reduce((least, current) => {\n const leastConnections = this.connectionCounts.get(least.id) || 0;\n const currentConnections = this.connectionCounts.get(current.id) || 0;\n return currentConnections < leastConnections ? current : least;\n });\n }\n\n /**\n * Weighted 전략\n */\n private selectWeighted(endpoints: WebhookEndpoint[]): WebhookEndpoint {\n const weights = this.config.weights || {};\n const totalWeight = endpoints.reduce((sum, endpoint) => {\n return sum + (weights[endpoint.id] || 1);\n }, 0);\n\n let random = Math.random() * totalWeight;\n \n for (const endpoint of endpoints) {\n const weight = weights[endpoint.id] || 1;\n random -= weight;\n if (random <= 0) {\n return endpoint;\n }\n }\n\n return endpoints[0]; // fallback\n }\n\n /**\n * Random 전략\n */\n private selectRandom(endpoints: WebhookEndpoint[]): WebhookEndpoint {\n const randomIndex = Math.floor(Math.random() * endpoints.length);\n return endpoints[randomIndex];\n }\n\n /**\n * Half-open Circuit Breaker 엔드포인트 시도\n */\n private tryHalfOpenEndpoint(endpoints: WebhookEndpoint[]): WebhookEndpoint | null {\n const now = new Date();\n \n for (const endpoint of endpoints) {\n const circuitBreaker = this.circuitBreakers.get(endpoint.id);\n \n if (circuitBreaker?.state === 'open' && circuitBreaker.nextRetryTime && now >= circuitBreaker.nextRetryTime) {\n circuitBreaker.state = 'half-open';\n this.emit('circuitBreakerHalfOpen', { endpointId: endpoint.id });\n return endpoint;\n }\n }\n\n return null;\n }\n\n /**\n * Circuit Breaker 상태 업데이트\n */\n private updateCircuitBreaker(endpointId: string, success: boolean): void {\n let circuitBreaker = this.circuitBreakers.get(endpointId);\n \n if (!circuitBreaker) {\n circuitBreaker = {\n endpointId,\n state: 'closed',\n failureCount: 0\n };\n this.circuitBreakers.set(endpointId, circuitBreaker);\n }\n\n if (!success) {\n circuitBreaker.failureCount++;\n circuitBreaker.lastFailureTime = new Date();\n\n // 5번 실패 시 Circuit Breaker 열기\n if (circuitBreaker.failureCount >= 5 && circuitBreaker.state === 'closed') {\n circuitBreaker.state = 'open';\n circuitBreaker.nextRetryTime = new Date(Date.now() + 60000); // 1분 후 재시도\n \n this.emit('circuitBreakerOpened', {\n endpointId,\n failureCount: circuitBreaker.failureCount,\n nextRetryTime: circuitBreaker.nextRetryTime\n });\n }\n }\n }\n\n /**\n * 연결 수 증가\n */\n private incrementConnections(endpointId: string): void {\n const currentCount = this.connectionCounts.get(endpointId) || 0;\n this.connectionCounts.set(endpointId, currentCount + 1);\n \n const health = this.endpointHealth.get(endpointId);\n if (health) {\n health.activeConnections = currentCount + 1;\n }\n }\n\n /**\n * 연결 수 감소\n */\n private decrementConnections(endpointId: string): void {\n const currentCount = this.connectionCounts.get(endpointId) || 0;\n const newCount = Math.max(0, currentCount - 1);\n this.connectionCounts.set(endpointId, newCount);\n \n const health = this.endpointHealth.get(endpointId);\n if (health) {\n health.activeConnections = newCount;\n }\n }\n\n /**\n * 엔드포인트 건강 상태 확인\n */\n private async checkEndpointHealth(endpoint: WebhookEndpoint): Promise<void> {\n const startTime = Date.now();\n \n try {\n // 간단한 HEAD 요청으로 건강 상태 확인\n const response = await fetch(endpoint.url, {\n method: 'HEAD',\n signal: AbortSignal.timeout(this.config.healthCheckTimeoutMs)\n });\n\n const responseTime = Date.now() - startTime;\n const success = response.ok;\n\n await this.onRequestComplete(endpoint.id, success, responseTime);\n\n this.emit('healthCheckCompleted', {\n endpointId: endpoint.id,\n success,\n responseTime,\n httpStatus: response.status\n });\n\n } catch (error) {\n const responseTime = Date.now() - startTime;\n await this.onRequestComplete(endpoint.id, false, responseTime);\n\n this.emit('healthCheckFailed', {\n endpointId: endpoint.id,\n error: error instanceof Error ? error.message : 'Unknown error',\n responseTime\n });\n }\n }\n\n /**\n * 건강 상태 확인 시작\n */\n private startHealthChecks(): void {\n this.healthCheckInterval = setInterval(async () => {\n const endpoints = Array.from(this.endpointHealth.keys());\n \n for (const endpointId of endpoints) {\n const health = this.endpointHealth.get(endpointId);\n if (health) {\n // 실제 구현에서는 엔드포인트 정보를 저장해야 함\n // 여기서는 모킹된 엔드포인트 사용\n const mockEndpoint: WebhookEndpoint = {\n id: endpointId,\n url: `https://webhook.example.com/${endpointId}`,\n active: true,\n events: [],\n createdAt: new Date(),\n updatedAt: new Date(),\n status: 'active'\n };\n \n await this.checkEndpointHealth(mockEndpoint);\n }\n }\n }, this.config.healthCheckInterval);\n }\n\n /**\n * 로드 밸런서 종료\n */\n async shutdown(): Promise<void> {\n if (this.healthCheckInterval) {\n clearInterval(this.healthCheckInterval);\n this.healthCheckInterval = null;\n }\n\n this.emit('shutdown', {\n totalEndpoints: this.endpointHealth.size,\n activeConnections: Array.from(this.connectionCounts.values()).reduce((sum, count) => sum + count, 0)\n });\n }\n}","/**\n * Endpoint Manager\n * 웹훅 엔드포인트 관리를 위한 고급 기능 제공\n */\n\nimport type { WebhookEndpoint } from '../types/webhook.types';\nimport { WebhookEventType } from '../types/webhook.types';\nimport type { EndpointFilter, PaginationOptions, SearchResult, StorageConfig } from './types';\nimport { EventEmitter } from 'events';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport class EndpointManager extends EventEmitter {\n private config: StorageConfig;\n private endpoints: Map<string, WebhookEndpoint> = new Map();\n private indexByUrl: Map<string, string> = new Map(); // url -> id\n private indexByEvent: Map<WebhookEventType, Set<string>> = new Map(); // event -> endpoint ids\n private indexByStatus: Map<string, Set<string>> = new Map(); // status -> endpoint ids\n\n private defaultConfig: StorageConfig = {\n type: 'memory',\n retentionDays: 90,\n enableEncryption: false\n };\n\n constructor(config: Partial<StorageConfig> = {}) {\n super();\n this.config = { ...this.defaultConfig, ...config };\n \n this.initializeIndexes();\n \n if (this.config.type === 'file' && this.config.filePath) {\n this.loadFromFile().catch(error => {\n this.emit('loadError', error);\n });\n }\n }\n\n /**\n * 엔드포인트 추가\n */\n async addEndpoint(endpoint: WebhookEndpoint): Promise<void> {\n // 중복 URL 확인\n if (this.indexByUrl.has(endpoint.url)) {\n const existingId = this.indexByUrl.get(endpoint.url);\n if (existingId !== endpoint.id) {\n throw new Error(`Endpoint with URL ${endpoint.url} already exists with different ID`);\n }\n }\n\n // 기존 엔드포인트가 있으면 인덱스에서 제거\n const existingEndpoint = this.endpoints.get(endpoint.id);\n if (existingEndpoint) {\n this.removeFromIndexes(existingEndpoint);\n }\n\n // 새 엔드포인트 저장\n this.endpoints.set(endpoint.id, endpoint);\n this.addToIndexes(endpoint);\n\n // 파일 저장\n if (this.config.type === 'file') {\n await this.saveToFile();\n }\n\n this.emit('endpointAdded', { endpointId: endpoint.id, url: endpoint.url });\n }\n\n /**\n * 엔드포인트 업데이트\n */\n async updateEndpoint(endpointId: string, updates: Partial<WebhookEndpoint>): Promise<WebhookEndpoint> {\n const existingEndpoint = this.endpoints.get(endpointId);\n if (!existingEndpoint) {\n throw new Error(`Endpoint ${endpointId} not found`);\n }\n\n // URL 변경 시 중복 확인\n if (updates.url && updates.url !== existingEndpoint.url) {\n if (this.indexByUrl.has(updates.url)) {\n const existingId = this.indexByUrl.get(updates.url);\n if (existingId !== endpointId) {\n throw new Error(`Endpoint with URL ${updates.url} already exists`);\n }\n }\n }\n\n // 인덱스에서 제거\n this.removeFromIndexes(existingEndpoint);\n\n // 업데이트 적용\n const updatedEndpoint: WebhookEndpoint = {\n ...existingEndpoint,\n ...updates,\n updatedAt: new Date()\n };\n\n this.endpoints.set(endpointId, updatedEndpoint);\n this.addToIndexes(updatedEndpoint);\n\n // 파일 저장\n if (this.config.type === 'file') {\n await this.saveToFile();\n }\n\n this.emit('endpointUpdated', { \n endpointId, \n changes: Object.keys(updates),\n oldUrl: existingEndpoint.url,\n newUrl: updatedEndpoint.url\n });\n\n return updatedEndpoint;\n }\n\n /**\n * 엔드포인트 제거\n */\n async removeEndpoint(endpointId: string): Promise<boolean> {\n const endpoint = this.endpoints.get(endpointId);\n if (!endpoint) {\n return false;\n }\n\n // 인덱스에서 제거\n this.removeFromIndexes(endpoint);\n this.endpoints.delete(endpointId);\n\n // 파일 저장\n if (this.config.type === 'file') {\n await this.saveToFile();\n }\n\n this.emit('endpointRemoved', { endpointId, url: endpoint.url });\n return true;\n }\n\n /**\n * 엔드포인트 조회\n */\n async getEndpoint(endpointId: string): Promise<WebhookEndpoint | null> {\n return this.endpoints.get(endpointId) || null;\n }\n\n /**\n * URL로 엔드포인트 조회\n */\n async getEndpointByUrl(url: string): Promise<WebhookEndpoint | null> {\n const endpointId = this.indexByUrl.get(url);\n return endpointId ? this.endpoints.get(endpointId) || null : null;\n }\n\n /**\n * 필터 조건에 맞는 엔드포인트 검색\n */\n async searchEndpoints(\n filter: EndpointFilter = {},\n pagination: PaginationOptions = { page: 1, limit: 100 }\n ): Promise<SearchResult<WebhookEndpoint>> {\n let candidateIds: Set<string> | null = null;\n\n // 상태 필터 적용\n if (filter.status) {\n const statusIds = this.indexByStatus.get(filter.status);\n candidateIds = statusIds ? new Set(statusIds) : new Set();\n }\n\n // 이벤트 필터 적용\n if (filter.events && filter.events.length > 0) {\n const eventIds = new Set<string>();\n for (const eventType of filter.events) {\n const ids = this.indexByEvent.get(eventType);\n if (ids) {\n ids.forEach(id => eventIds.add(id));\n }\n }\n \n if (candidateIds) {\n candidateIds = new Set(Array.from(candidateIds).filter(id => eventIds.has(id)));\n } else {\n candidateIds = eventIds;\n }\n }\n\n // 후보가 없으면 모든 엔드포인트를 대상으로\n if (!candidateIds) {\n candidateIds = new Set(this.endpoints.keys());\n }\n\n // 추가 필터 적용\n const filteredEndpoints = Array.from(candidateIds)\n .map(id => this.endpoints.get(id)!)\n .filter(endpoint => this.matchesFilter(endpoint, filter));\n\n // 정렬\n if (pagination.sortBy) {\n filteredEndpoints.sort((a, b) => {\n const aValue = this.getFieldValue(a, pagination.sortBy!);\n const bValue = this.getFieldValue(b, pagination.sortBy!);\n \n let comparison = 0;\n if (aValue < bValue) comparison = -1;\n else if (aValue > bValue) comparison = 1;\n \n return pagination.sortOrder === 'desc' ? -comparison : comparison;\n });\n }\n\n // 페이지네이션 적용\n const totalCount = filteredEndpoints.length;\n const totalPages = Math.ceil(totalCount / pagination.limit);\n const startIndex = (pagination.page - 1) * pagination.limit;\n const endIndex = startIndex + pagination.limit;\n const items = filteredEndpoints.slice(startIndex, endIndex);\n\n return {\n items,\n totalCount,\n page: pagination.page,\n totalPages,\n hasNext: pagination.page < totalPages,\n hasPrevious: pagination.page > 1\n };\n }\n\n /**\n * 특정 이벤트 타입을 구독하는 활성 엔드포인트 조회\n */\n async getActiveEndpointsForEvent(eventType: WebhookEventType): Promise<WebhookEndpoint[]> {\n const endpointIds = this.indexByEvent.get(eventType);\n if (!endpointIds) {\n return [];\n }\n\n return Array.from(endpointIds)\n .map(id => this.endpoints.get(id)!)\n .filter(endpoint => endpoint.status === 'active');\n }\n\n /**\n * 엔드포인트 통계 조회\n */\n getStats(): {\n totalEndpoints: number;\n activeEndpoints: number;\n inactiveEndpoints: number;\n errorEndpoints: number;\n suspendedEndpoints: number;\n eventSubscriptions: Record<WebhookEventType, number>;\n } {\n const total = this.endpoints.size;\n const active = this.indexByStatus.get('active')?.size || 0;\n const inactive = this.indexByStatus.get('inactive')?.size || 0;\n const error = this.indexByStatus.get('error')?.size || 0;\n const suspended = this.indexByStatus.get('suspended')?.size || 0;\n\n const eventSubscriptions: Record<WebhookEventType, number> = {} as any;\n for (const [eventType, endpointIds] of this.indexByEvent.entries()) {\n eventSubscriptions[eventType] = endpointIds.size;\n }\n\n return {\n totalEndpoints: total,\n activeEndpoints: active,\n inactiveEndpoints: inactive,\n errorEndpoints: error,\n suspendedEndpoints: suspended,\n eventSubscriptions\n };\n }\n\n /**\n * 만료된 엔드포인트 정리\n */\n async cleanupExpiredEndpoints(): Promise<number> {\n if (!this.config.retentionDays) {\n return 0;\n }\n\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays);\n\n const expiredEndpoints = Array.from(this.endpoints.values()).filter(endpoint => {\n return endpoint.status === 'inactive' && \n (!endpoint.lastTriggeredAt || endpoint.lastTriggeredAt < cutoffDate);\n });\n\n for (const endpoint of expiredEndpoints) {\n await this.removeEndpoint(endpoint.id);\n }\n\n if (expiredEndpoints.length > 0) {\n this.emit('expiredEndpointsCleanup', { \n removedCount: expiredEndpoints.length,\n cutoffDate \n });\n }\n\n return expiredEndpoints.length;\n }\n\n /**\n * 인덱스 초기화\n */\n private initializeIndexes(): void {\n // 이벤트 타입별 인덱스 초기화\n const eventTypes = Object.values(WebhookEventType);\n for (const eventType of eventTypes) {\n this.indexByEvent.set(eventType, new Set());\n }\n\n // 상태별 인덱스 초기화\n const statuses = ['active', 'inactive', 'error', 'suspended'];\n for (const status of statuses) {\n this.indexByStatus.set(status, new Set());\n }\n }\n\n /**\n * 인덱스에 엔드포인트 추가\n */\n private addToIndexes(endpoint: WebhookEndpoint): void {\n // URL 인덱스\n this.indexByUrl.set(endpoint.url, endpoint.id);\n\n // 상태 인덱스\n const statusSet = this.indexByStatus.get(endpoint.status);\n if (statusSet) {\n statusSet.add(endpoint.id);\n }\n\n // 이벤트 인덱스\n for (const eventType of endpoint.events) {\n const eventSet = this.indexByEvent.get(eventType);\n if (eventSet) {\n eventSet.add(endpoint.id);\n }\n }\n }\n\n /**\n * 인덱스에서 엔드포인트 제거\n */\n private removeFromIndexes(endpoint: WebhookEndpoint): void {\n // URL 인덱스\n this.indexByUrl.delete(endpoint.url);\n\n // 상태 인덱스\n const statusSet = this.indexByStatus.get(endpoint.status);\n if (statusSet) {\n statusSet.delete(endpoint.id);\n }\n\n // 이벤트 인덱스\n for (const eventType of endpoint.events) {\n const eventSet = this.indexByEvent.get(eventType);\n if (eventSet) {\n eventSet.delete(endpoint.id);\n }\n }\n }\n\n /**\n * 필터 조건 매칭 확인\n */\n private matchesFilter(endpoint: WebhookEndpoint, filter: EndpointFilter): boolean {\n // 프로바이더 ID 필터\n if (filter.providerId && filter.providerId.length > 0) {\n const hasMatchingProvider = filter.providerId.some(providerId => \n endpoint.filters?.providerId?.includes(providerId)\n );\n if (!hasMatchingProvider) return false;\n }\n\n // 채널 ID 필터\n if (filter.channelId && filter.channelId.length > 0) {\n const hasMatchingChannel = filter.channelId.some(channelId => \n endpoint.filters?.channelId?.includes(channelId)\n );\n if (!hasMatchingChannel) return false;\n }\n\n // 생성 날짜 필터\n if (filter.createdAfter && endpoint.createdAt < filter.createdAfter) {\n return false;\n }\n if (filter.createdBefore && endpoint.createdAt > filter.createdBefore) {\n return false;\n }\n\n // 마지막 트리거 날짜 필터\n if (filter.lastTriggeredAfter && (!endpoint.lastTriggeredAt || endpoint.lastTriggeredAt < filter.lastTriggeredAfter)) {\n return false;\n }\n if (filter.lastTriggeredBefore && (!endpoint.lastTriggeredAt || endpoint.lastTriggeredAt > filter.lastTriggeredBefore)) {\n return false;\n }\n\n return true;\n }\n\n /**\n * 객체 필드 값 가져오기 (정렬용)\n */\n private getFieldValue(obj: any, fieldPath: string): any {\n return fieldPath.split('.').reduce((value, key) => value?.[key], obj);\n }\n\n /**\n * 파일에서 데이터 로드\n */\n private async loadFromFile(): Promise<void> {\n if (!this.config.filePath) return;\n\n try {\n const data = await fs.readFile(this.config.filePath, 'utf8');\n const parsed = JSON.parse(data);\n \n // 엔드포인트 복원\n for (const endpointData of parsed.endpoints || []) {\n const endpoint: WebhookEndpoint = {\n ...endpointData,\n createdAt: new Date(endpointData.createdAt),\n updatedAt: new Date(endpointData.updatedAt),\n lastTriggeredAt: endpointData.lastTriggeredAt ? new Date(endpointData.lastTriggeredAt) : undefined\n };\n \n this.endpoints.set(endpoint.id, endpoint);\n this.addToIndexes(endpoint);\n }\n\n this.emit('dataLoaded', { \n filePath: this.config.filePath,\n endpointCount: this.endpoints.size \n });\n\n } catch (error) {\n if ((error as any).code !== 'ENOENT') {\n this.emit('loadError', error);\n }\n }\n }\n\n /**\n * 파일에 데이터 저장\n */\n private async saveToFile(): Promise<void> {\n if (!this.config.filePath) return;\n\n try {\n const data = {\n endpoints: Array.from(this.endpoints.values()),\n savedAt: new Date().toISOString()\n };\n\n const json = JSON.stringify(data, null, 2);\n \n // 디렉토리 생성\n await fs.mkdir(path.dirname(this.config.filePath), { recursive: true });\n \n // 파일 저장\n await fs.writeFile(this.config.filePath, json, 'utf8');\n\n this.emit('dataSaved', { \n filePath: this.config.filePath,\n endpointCount: this.endpoints.size \n });\n\n } catch (error) {\n this.emit('saveError', error);\n throw error;\n }\n }\n\n /**\n * 엔드포인트 관리자 종료\n */\n async shutdown(): Promise<void> {\n // 마지막 저장\n if (this.config.type === 'file') {\n await this.saveToFile().catch(error => {\n this.emit('saveError', error);\n });\n }\n\n this.emit('shutdown', { endpointCount: this.endpoints.size });\n }\n}","/**\n * Delivery Store\n * 웹훅 전달 기록 저장 및 관리\n */\n\nimport type { WebhookDelivery, WebhookEventType } from '../types/webhook.types';\nimport type { DeliveryFilter, PaginationOptions, SearchResult, StorageConfig } from './types';\nimport { EventEmitter } from 'events';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport class DeliveryStore extends EventEmitter {\n private config: StorageConfig;\n private deliveries: Map<string, WebhookDelivery> = new Map();\n private indexByEndpoint: Map<string, Set<string>> = new Map(); // endpointId -> delivery ids\n private indexByStatus: Map<string, Set<string>> = new Map(); // status -> delivery ids\n private indexByDate: Map<string, Set<string>> = new Map(); // YYYY-MM-DD -> delivery ids\n private cleanupInterval: NodeJS.Timeout | null = null;\n\n private defaultConfig: StorageConfig = {\n type: 'memory',\n retentionDays: 30,\n enableCompression: false,\n maxMemoryUsage: 100 * 1024 * 1024 // 100MB\n };\n\n constructor(config: Partial<StorageConfig> = {}) {\n super();\n this.config = { ...this.defaultConfig, ...config };\n \n this.initializeIndexes();\n this.startCleanupTask();\n \n if (this.config.type === 'file' && this.config.filePath) {\n this.loadFromFile().catch(error => {\n this.emit('loadError', error);\n });\n }\n }\n\n /**\n * 전달 기록 저장\n */\n async saveDelivery(delivery: WebhookDelivery): Promise<void> {\n // 기존 기록이 있으면 인덱스에서 제거\n const existingDelivery = this.deliveries.get(delivery.id);\n if (existingDelivery) {\n this.removeFromIndexes(existingDelivery);\n }\n\n // 메모리 사용량 확인\n if (this.config.type === 'memory' && this.config.maxMemoryUsage) {\n await this.checkMemoryUsage();\n }\n\n // 새 기록 저장\n this.deliveries.set(delivery.id, delivery);\n this.addToIndexes(delivery);\n\n // 파일 저장\n if (this.config.type === 'file') {\n await this.appendToFile(delivery);\n }\n\n this.emit('deliverySaved', { \n deliveryId: delivery.id, \n endpointId: delivery.endpointId,\n status: delivery.status \n });\n }\n\n /**\n * 전달 기록 조회\n */\n async getDelivery(deliveryId: string): Promise<WebhookDelivery | null> {\n return this.deliveries.get(deliveryId) || null;\n }\n\n /**\n * 필터 조건에 맞는 전달 기록 검색\n */\n async searchDeliveries(\n filter: DeliveryFilter = {},\n pagination: PaginationOptions = { page: 1, limit: 100 }\n ): Promise<SearchResult<WebhookDelivery>> {\n let candidateIds: Set<string> | null = null;\n\n // 엔드포인트 필터 적용\n if (filter.endpointId) {\n const endpointIds = this.indexByEndpoint.get(filter.endpointId);\n candidateIds = endpointIds ? new Set(endpointIds) : new Set();\n }\n\n // 상태 필터 적용\n if (filter.status) {\n const statusIds = this.indexByStatus.get(filter.status);\n if (candidateIds) {\n candidateIds = new Set(Array.from(candidateIds).filter(id => statusIds?.has(id)));\n } else {\n candidateIds = statusIds ? new Set(statusIds) : new Set();\n }\n }\n\n // 날짜 범위 필터 적용\n if (filter.createdAfter || filter.createdBefore) {\n const dateIds = this.getDeliveryIdsByDateRange(filter.createdAfter, filter.createdBefore);\n if (candidateIds) {\n candidateIds = new Set(Array.from(candidateIds).filter(id => dateIds.has(id)));\n } else {\n candidateIds = dateIds;\n }\n }\n\n // 후보가 없으면 모든 전달 기록을 대상으로\n if (!candidateIds) {\n candidateIds = new Set(this.deliveries.keys());\n }\n\n // 추가 필터 적용\n const filteredDeliveries = Array.from(candidateIds)\n .map(id => this.deliveries.get(id)!)\n .filter(delivery => this.matchesFilter(delivery, filter));\n\n // 정렬 (기본: 최신순)\n filteredDeliveries.sort((a, b) => {\n if (pagination.sortBy === 'createdAt' || !pagination.sortBy) {\n const comparison = b.createdAt.getTime() - a.createdAt.getTime();\n return pagination.sortOrder === 'asc' ? -comparison : comparison;\n }\n \n const aValue = this.getFieldValue(a, pagination.sortBy);\n const bValue = this.getFieldValue(b, pagination.sortBy);\n \n let comparison = 0;\n if (aValue < bValue) comparison = -1;\n else if (aValue > bValue) comparison = 1;\n \n return pagination.sortOrder === 'desc' ? -comparison : comparison;\n });\n\n // 페이지네이션 적용\n const totalCount = filteredDeliveries.length;\n const totalPages = Math.ceil(totalCount / pagination.limit);\n const startIndex = (pagination.page - 1) * pagination.limit;\n const endIndex = startIndex + pagination.limit;\n const items = filteredDeliveries.slice(startIndex, endIndex);\n\n return {\n items,\n totalCount,\n page: pagination.page,\n totalPages,\n hasNext: pagination.page < totalPages,\n hasPrevious: pagination.page > 1\n };\n }\n\n /**\n * 엔드포인트별 전달 기록 조회\n */\n async getDeliveriesByEndpoint(\n endpointId: string,\n limit = 100\n ): Promise<WebhookDelivery[]> {\n const deliveryIds = this.indexByEndpoint.get(endpointId);\n if (!deliveryIds) {\n return [];\n }\n\n return Array.from(deliveryIds)\n .map(id => this.deliveries.get(id)!)\n .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())\n .slice(0, limit);\n }\n\n /**\n * 실패한 전달 기록 조회\n */\n async getFailedDeliveries(\n endpointId?: string,\n limit = 100\n ): Promise<WebhookDelivery[]> {\n const filter: DeliveryFilter = { \n status: 'failed',\n endpointId \n };\n \n const result = await this.searchDeliveries(filter, { page: 1, limit });\n return result.items;\n }\n\n /**\n * 전달 통계 조회\n */\n async getDeliveryStats(\n endpointId?: string,\n timeRange?: { start: Date; end: Date }\n ): Promise<{\n totalDeliveries: number;\n successfulDeliveries: number;\n failedDeliveries: number;\n pendingDeliveries: number;\n exhaustedDeliveries: number;\n averageLatency: number;\n successRate: number;\n errorBreakdown: Record<string, number>;\n }> {\n const filter: DeliveryFilter = {\n endpointId,\n createdAfter: timeRange?.start,\n createdBefore: timeRange?.end\n };\n\n const result = await this.searchDeliveries(filter, { page: 1, limit: 10000 });\n const deliveries = result.items;\n\n const successful = deliveries.filter(d => d.status === 'success');\n const failed = deliveries.filter(d => d.status === 'failed');\n const pending = deliveries.filter(d => d.status === 'pending');\n const exhausted = deliveries.filter(d => d.status === 'exhausted');\n\n // 평균 레이턴시 계산\n const completedDeliveries = deliveries.filter(d => d.completedAt);\n const totalLatency = completedDeliveries.reduce((sum, delivery) => {\n const lastAttempt = delivery.attempts[delivery.attempts.length - 1];\n return sum + (lastAttempt?.latencyMs || 0);\n }, 0);\n const averageLatency = completedDeliveries.length > 0 ? totalLatency / completedDeliveries.length : 0;\n\n // 에러 유형별 분석\n const errorBreakdown: Record<string, number> = {};\n for (const delivery of [...failed, ...exhausted]) {\n const lastAttempt = delivery.attempts[delivery.attempts.length - 1];\n if (lastAttempt?.error) {\n errorBreakdown[lastAttempt.error] = (errorBreakdown[lastAttempt.error] || 0) + 1;\n } else if (lastAttempt?.httpStatus) {\n const errorKey = `HTTP ${lastAttempt.httpStatus}`;\n errorBreakdown[errorKey] = (errorBreakdown[errorKey] || 0) + 1;\n }\n }\n\n return {\n totalDeliveries: deliveries.length,\n successfulDeliveries: successful.length,\n failedDeliveries: failed.length,\n pendingDeliveries: pending.length,\n exhaustedDeliveries: exhausted.length,\n averageLatency,\n successRate: deliveries.length > 0 ? (successful.length / deliveries.length) * 100 : 0,\n errorBreakdown\n };\n }\n\n /**\n * 오래된 전달 기록 정리\n */\n async cleanupOldDeliveries(): Promise<number> {\n if (!this.config.retentionDays) {\n return 0;\n }\n\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays);\n\n const oldDeliveries = Array.from(this.deliveries.values()).filter(delivery => \n delivery.createdAt < cutoffDate\n );\n\n for (const delivery of oldDeliveries) {\n this.removeFromIndexes(delivery);\n this.deliveries.delete(delivery.id);\n }\n\n if (oldDeliveries.length > 0) {\n this.emit('oldDeliveriesCleanup', { \n removedCount: oldDeliveries.length,\n cutoffDate \n });\n\n // 파일 저장\n if (this.config.type === 'file') {\n await this.saveToFile();\n }\n }\n\n return oldDeliveries.length;\n }\n\n /**\n * 저장소 통계 조회\n */\n getStorageStats(): {\n totalDeliveries: number;\n memoryUsage: number;\n indexSizes: {\n byEndpoint: number;\n byStatus: number;\n byDate: number;\n };\n } {\n const memoryUsage = this.estimateMemoryUsage();\n \n return {\n totalDeliveries: this.deliveries.size,\n memoryUsage,\n indexSizes: {\n byEndpoint: this.indexByEndpoint.size,\n byStatus: this.indexByStatus.size,\n byDate: this.indexByDate.size\n }\n };\n }\n\n /**\n * 인덱스 초기화\n */\n private initializeIndexes(): void {\n const statuses = ['pending', 'success', 'failed', 'exhausted'];\n for (const status of statuses) {\n this.indexByStatus.set(status, new Set());\n }\n }\n\n /**\n * 인덱스에 전달 기록 추가\n */\n private addToIndexes(delivery: WebhookDelivery): void {\n // 엔드포인트 인덱스\n if (!this.indexByEndpoint.has(delivery.endpointId)) {\n this.indexByEndpoint.set(delivery.endpointId, new Set());\n }\n this.indexByEndpoint.get(delivery.endpointId)!.add(delivery.id);\n\n // 상태 인덱스\n const statusSet = this.indexByStatus.get(delivery.status);\n if (statusSet) {\n statusSet.add(delivery.id);\n }\n\n // 날짜 인덱스\n const dateKey = delivery.createdAt.toISOString().split('T')[0];\n if (!this.indexByDate.has(dateKey)) {\n this.indexByDate.set(dateKey, new Set());\n }\n this.indexByDate.get(dateKey)!.add(delivery.id);\n }\n\n /**\n * 인덱스에서 전달 기록 제거\n */\n private removeFromIndexes(delivery: WebhookDelivery): void {\n // 엔드포인트 인덱스\n const endpointSet = this.indexByEndpoint.get(delivery.endpointId);\n if (endpointSet) {\n endpointSet.delete(delivery.id);\n if (endpointSet.size === 0) {\n this.indexByEndpoint.delete(delivery.endpointId);\n }\n }\n\n // 상태 인덱스\n const statusSet = this.indexByStatus.get(delivery.status);\n if (statusSet) {\n statusSet.delete(delivery.id);\n }\n\n // 날짜 인덱스\n const dateKey = delivery.createdAt.toISOString().split('T')[0];\n const dateSet = this.indexByDate.get(dateKey);\n if (dateSet) {\n dateSet.delete(delivery.id);\n if (dateSet.size === 0) {\n this.indexByDate.delete(dateKey);\n }\n }\n }\n\n /**\n * 날짜 범위로 전달 기록 ID 조회\n */\n private getDeliveryIdsByDateRange(startDate?: Date, endDate?: Date): Set<string> {\n const ids = new Set<string>();\n \n for (const [dateKey, deliveryIds] of this.indexByDate.entries()) {\n const date = new Date(dateKey);\n \n if (startDate && date < startDate) continue;\n if (endDate && date > endDate) continue;\n \n deliveryIds.forEach(id => ids.add(id));\n }\n \n return ids;\n }\n\n /**\n * 필터 조건 매칭 확인\n */\n private matchesFilter(delivery: WebhookDelivery, filter: DeliveryFilter): boolean {\n // 이벤트 ID 필터\n if (filter.eventId && delivery.eventId !== filter.eventId) {\n return false;\n }\n\n // HTTP 상태 코드 필터\n if (filter.httpStatusCode && filter.httpStatusCode.length > 0) {\n const lastAttempt = delivery.attempts[delivery.attempts.length - 1];\n if (!lastAttempt?.httpStatus || !filter.httpStatusCode.includes(lastAttempt.httpStatus)) {\n return false;\n }\n }\n\n // 에러 존재 여부 필터\n if (filter.hasError !== undefined) {\n const hasError = delivery.attempts.some(attempt => attempt.error);\n if (filter.hasError !== hasError) {\n return false;\n }\n }\n\n // 완료 날짜 필터\n if (filter.completedAfter && (!delivery.completedAt || delivery.completedAt < filter.completedAfter)) {\n return false;\n }\n if (filter.completedBefore && (!delivery.completedAt || delivery.completedAt > filter.completedBefore)) {\n return false;\n }\n\n return true;\n }\n\n /**\n * 객체 필드 값 가져오기\n */\n private getFieldValue(obj: any, fieldPath: string): any {\n return fieldPath.split('.').reduce((value, key) => value?.[key], obj);\n }\n\n /**\n * 메모리 사용량 추정\n */\n private estimateMemoryUsage(): number {\n let totalSize = 0;\n \n for (const delivery of this.deliveries.values()) {\n // 대략적인 객체 크기 계산\n totalSize += JSON.stringify(delivery).length * 2; // UTF-16 기준\n }\n \n return totalSize;\n }\n\n /**\n * 메모리 사용량 확인 및 정리\n */\n private async checkMemoryUsage(): Promise<void> {\n if (!this.config.maxMemoryUsage) return;\n\n const currentUsage = this.estimateMemoryUsage();\n \n if (currentUsage > this.config.maxMemoryUsage) {\n // 오래된 전달 기록부터 제거\n const deliveries = Array.from(this.deliveries.values())\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n \n let removedCount = 0;\n const targetUsage = this.config.maxMemoryUsage * 0.8; // 80%까지 줄이기\n \n for (const delivery of deliveries) {\n if (this.estimateMemoryUsage() <= targetUsage) break;\n \n this.removeFromIndexes(delivery);\n this.deliveries.delete(delivery.id);\n removedCount++;\n }\n \n if (removedCount > 0) {\n this.emit('memoryCleanup', { \n removedCount, \n previousUsage: currentUsage,\n currentUsage: this.estimateMemoryUsage() \n });\n }\n }\n }\n\n /**\n * 정리 작업 시작\n */\n private startCleanupTask(): void {\n // 1시간마다 정리 작업 실행\n this.cleanupInterval = setInterval(async () => {\n try {\n await this.cleanupOldDeliveries();\n await this.checkMemoryUsage();\n } catch (error) {\n this.emit('cleanupError', error);\n }\n }, 60 * 60 * 1000);\n }\n\n /**\n * 파일에 전달 기록 추가\n */\n private async appendToFile(delivery: WebhookDelivery): Promise<void> {\n if (!this.config.filePath) return;\n\n try {\n const line = JSON.stringify(delivery) + '\\n';\n await fs.appendFile(this.config.filePath, line, 'utf8');\n } catch (error) {\n this.emit('appendError', error);\n }\n }\n\n /**\n * 파일에서 데이터 로드\n */\n private async loadFromFile(): Promise<void> {\n if (!this.config.filePath) return;\n\n try {\n const data = await fs.readFile(this.config.filePath, 'utf8');\n const lines = data.trim().split('\\n').filter(line => line.trim());\n \n for (const line of lines) {\n try {\n const deliveryData = JSON.parse(line);\n const delivery: WebhookDelivery = {\n ...deliveryData,\n createdAt: new Date(deliveryData.createdAt),\n completedAt: deliveryData.completedAt ? new Date(deliveryData.completedAt) : undefined,\n nextRetryAt: deliveryData.nextRetryAt ? new Date(deliveryData.nextRetryAt) : undefined,\n attempts: deliveryData.attempts.map((attempt: any) => ({\n ...attempt,\n timestamp: new Date(attempt.timestamp)\n }))\n };\n \n this.deliveries.set(delivery.id, delivery);\n this.addToIndexes(delivery);\n } catch (parseError) {\n this.emit('parseError', { line, error: parseError });\n }\n }\n\n this.emit('dataLoaded', { \n filePath: this.config.filePath,\n deliveryCount: this.deliveries.size \n });\n\n } catch (error) {\n if ((error as any).code !== 'ENOENT') {\n this.emit('loadError', error);\n }\n }\n }\n\n /**\n * 파일에 데이터 저장\n */\n private async saveToFile(): Promise<void> {\n if (!this.config.filePath) return;\n\n try {\n const lines = Array.from(this.deliveries.values())\n .map(delivery => JSON.stringify(delivery))\n .join('\\n');\n \n // 디렉토리 생성\n await fs.mkdir(path.dirname(this.config.filePath), { recursive: true });\n \n // 파일 저장\n await fs.writeFile(this.config.filePath, lines + '\\n', 'utf8');\n\n this.emit('dataSaved', { \n filePath: this.config.filePath,\n deliveryCount: this.deliveries.size \n });\n\n } catch (error) {\n this.emit('saveError', error);\n throw error;\n }\n }\n\n /**\n * 전달 저장소 종료\n */\n async shutdown(): Promise<void> {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n\n // 마지막 저장\n if (this.config.type === 'file') {\n await this.saveToFile().catch(error => {\n this.emit('saveError', error);\n });\n }\n\n this.emit('shutdown', { deliveryCount: this.deliveries.size });\n }\n}","/**\n * Event Store\n * 웹훅 이벤트 저장 및 관리\n */\n\nimport type { WebhookEvent } from '../types/webhook.types';\nimport { WebhookEventType } from '../types/webhook.types';\nimport type { EventFilter, PaginationOptions, SearchResult, StorageConfig } from './types';\nimport { EventEmitter } from 'events';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport class EventStore extends EventEmitter {\n private config: StorageConfig;\n private events: Map<string, WebhookEvent> = new Map();\n private indexByType: Map<WebhookEventType, Set<string>> = new Map();\n private indexByDate: Map<string, Set<string>> = new Map(); // YYYY-MM-DD -> event ids\n private indexByProvider: Map<string, Set<string>> = new Map();\n private indexByChannel: Map<string, Set<string>> = new Map();\n private cleanupInterval: NodeJS.Timeout | null = null;\n\n private defaultConfig: StorageConfig = {\n type: 'memory',\n retentionDays: 7,\n enableCompression: false,\n maxMemoryUsage: 50 * 1024 * 1024 // 50MB\n };\n\n constructor(config: Partial<StorageConfig> = {}) {\n super();\n this.config = { ...this.defaultConfig, ...config };\n \n this.initializeIndexes();\n this.startCleanupTask();\n \n if (this.config.type === 'file' && this.config.filePath) {\n this.loadFromFile().catch(error => {\n this.emit('loadError', error);\n });\n }\n }\n\n /**\n * 이벤트 저장\n */\n async saveEvent(event: WebhookEvent): Promise<void> {\n // 중복 이벤트 확인\n if (this.events.has(event.id)) {\n this.emit('duplicateEvent', { eventId: event.id });\n return;\n }\n\n // 메모리 사용량 확인\n if (this.config.type === 'memory' && this.config.maxMemoryUsage) {\n await this.checkMemoryUsage();\n }\n\n // 이벤트 저장\n this.events.set(event.id, event);\n this.addToIndexes(event);\n\n // 파일 저장\n if (this.config.type === 'file') {\n await this.appendToFile(event);\n }\n\n this.emit('eventSaved', { \n eventId: event.id, \n type: event.type,\n providerId: event.metadata.providerId \n });\n }\n\n /**\n * 이벤트 조회\n */\n async getEvent(eventId: string): Promise<WebhookEvent | null> {\n return this.events.get(eventId) || null;\n }\n\n /**\n * 필터 조건에 맞는 이벤트 검색\n */\n async searchEvents(\n filter: EventFilter = {},\n pagination: PaginationOptions = { page: 1, limit: 100 }\n ): Promise<SearchResult<WebhookEvent>> {\n let candidateIds: Set<string> | null = null;\n\n // 이벤트 타입 필터 적용\n if (filter.type && filter.type.length > 0) {\n const typeIds = new Set<string>();\n for (const eventType of filter.type) {\n const ids = this.indexByType.get(eventType);\n if (ids) {\n ids.forEach(id => typeIds.add(id));\n }\n }\n candidateIds = typeIds;\n }\n\n // 프로바이더 필터 적용\n if (filter.providerId && filter.providerId.length > 0) {\n const providerIds = new Set<string>();\n for (const providerId of filter.providerId) {\n const ids = this.indexByProvider.get(providerId);\n if (ids) {\n ids.forEach(id => providerIds.add(id));\n }\n }\n \n if (candidateIds) {\n candidateIds = new Set(Array.from(candidateIds).filter(id => providerIds.has(id)));\n } else {\n candidateIds = providerIds;\n }\n }\n\n // 채널 필터 적용\n if (filter.channelId && filter.channelId.length > 0) {\n const channelIds = new Set<string>();\n for (const channelId of filter.channelId) {\n const ids = this.indexByChannel.get(channelId);\n if (ids) {\n ids.forEach(id => channelIds.add(id));\n }\n }\n \n if (candidateIds) {\n candidateIds = new Set(Array.from(candidateIds).filter(id => channelIds.has(id)));\n } else {\n candidateIds = channelIds;\n }\n }\n\n // 날짜 범위 필터 적용\n if (filter.createdAfter || filter.createdBefore) {\n const dateIds = this.getEventIdsByDateRange(filter.createdAfter, filter.createdBefore);\n if (candidateIds) {\n candidateIds = new Set(Array.from(candidateIds).filter(id => dateIds.has(id)));\n } else {\n candidateIds = dateIds;\n }\n }\n\n // 후보가 없으면 모든 이벤트를 대상으로\n if (!candidateIds) {\n candidateIds = new Set(this.events.keys());\n }\n\n // 추가 필터 적용\n const filteredEvents = Array.from(candidateIds)\n .map(id => this.events.get(id)!)\n .filter(event => this.matchesFilter(event, filter));\n\n // 정렬 (기본: 최신순)\n filteredEvents.sort((a, b) => {\n if (pagination.sortBy === 'timestamp' || !pagination.sortBy) {\n const comparison = b.timestamp.getTime() - a.timestamp.getTime();\n return pagination.sortOrder === 'asc' ? -comparison : comparison;\n }\n \n const aValue = this.getFieldValue(a, pagination.sortBy);\n const bValue = this.getFieldValue(b, pagination.sortBy);\n \n let comparison = 0;\n if (aValue < bValue) comparison = -1;\n else if (aValue > bValue) comparison = 1;\n \n return pagination.sortOrder === 'desc' ? -comparison : comparison;\n });\n\n // 페이지네이션 적용\n const totalCount = filteredEvents.length;\n const totalPages = Math.ceil(totalCount / pagination.limit);\n const startIndex = (pagination.page - 1) * pagination.limit;\n const endIndex = startIndex + pagination.limit;\n const items = filteredEvents.slice(startIndex, endIndex);\n\n return {\n items,\n totalCount,\n page: pagination.page,\n totalPages,\n hasNext: pagination.page < totalPages,\n hasPrevious: pagination.page > 1\n };\n }\n\n /**\n * 이벤트 타입별 조회\n */\n async getEventsByType(\n eventType: WebhookEventType,\n limit = 100\n ): Promise<WebhookEvent[]> {\n const eventIds = this.indexByType.get(eventType);\n if (!eventIds) {\n return [];\n }\n\n return Array.from(eventIds)\n .map(id => this.events.get(id)!)\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, limit);\n }\n\n /**\n * 이벤트 통계 조회\n */\n async getEventStats(\n timeRange?: { start: Date; end: Date }\n ): Promise<{\n totalEvents: number;\n eventsByType: Record<WebhookEventType, number>;\n eventsByProvider: Record<string, number>;\n eventsByChannel: Record<string, number>;\n eventsPerHour: Record<string, number>;\n }> {\n const filter: EventFilter = {\n createdAfter: timeRange?.start,\n createdBefore: timeRange?.end\n };\n\n const result = await this.searchEvents(filter, { page: 1, limit: 10000 });\n const events = result.items;\n\n // 타입별 집계\n const eventsByType: Record<WebhookEventType, number> = {} as any;\n for (const eventType of Object.values(WebhookEventType)) {\n eventsByType[eventType] = 0;\n }\n\n // 프로바이더별 집계\n const eventsByProvider: Record<string, number> = {};\n \n // 채널별 집계\n const eventsByChannel: Record<string, number> = {};\n \n // 시간별 집계\n const eventsPerHour: Record<string, number> = {};\n\n for (const event of events) {\n // 타입별\n eventsByType[event.type]++;\n\n // 프로바이더별\n if (event.metadata.providerId) {\n eventsByProvider[event.metadata.providerId] = (eventsByProvider[event.metadata.providerId] || 0) + 1;\n }\n\n // 채널별\n if (event.metadata.channelId) {\n eventsByChannel[event.metadata.channelId] = (eventsByChannel[event.metadata.channelId] || 0) + 1;\n }\n\n // 시간별 (YYYY-MM-DD HH 형식)\n const hourKey = event.timestamp.toISOString().substring(0, 13);\n eventsPerHour[hourKey] = (eventsPerHour[hourKey] || 0) + 1;\n }\n\n return {\n totalEvents: events.length,\n eventsByType,\n eventsByProvider,\n eventsByChannel,\n eventsPerHour\n };\n }\n\n /**\n * 오래된 이벤트 정리\n */\n async cleanupOldEvents(): Promise<number> {\n if (!this.config.retentionDays) {\n return 0;\n }\n\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays);\n\n const oldEvents = Array.from(this.events.values()).filter(event => \n event.timestamp < cutoffDate\n );\n\n for (const event of oldEvents) {\n this.removeFromIndexes(event);\n this.events.delete(event.id);\n }\n\n if (oldEvents.length > 0) {\n this.emit('oldEventsCleanup', { \n removedCount: oldEvents.length,\n cutoffDate \n });\n\n // 파일 저장\n if (this.config.type === 'file') {\n await this.saveToFile();\n }\n }\n\n return oldEvents.length;\n }\n\n /**\n * 중복 이벤트 정리\n */\n async cleanupDuplicateEvents(): Promise<number> {\n const eventsByContent = new Map<string, WebhookEvent[]>();\n \n // 이벤트를 내용별로 그룹화\n for (const event of this.events.values()) {\n const contentKey = this.generateContentKey(event);\n if (!eventsByContent.has(contentKey)) {\n eventsByContent.set(contentKey, []);\n }\n eventsByContent.get(contentKey)!.push(event);\n }\n\n let removedCount = 0;\n \n // 중복된 이벤트 제거 (가장 최신 것만 유지)\n for (const [contentKey, duplicateEvents] of eventsByContent.entries()) {\n if (duplicateEvents.length > 1) {\n // 타임스탬프 기준 정렬\n duplicateEvents.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());\n \n // 첫 번째(최신)를 제외한 나머지 제거\n for (let i = 1; i < duplicateEvents.length; i++) {\n const eventToRemove = duplicateEvents[i];\n this.removeFromIndexes(eventToRemove);\n this.events.delete(eventToRemove.id);\n removedCount++;\n }\n }\n }\n\n if (removedCount > 0) {\n this.emit('duplicateEventsCleanup', { removedCount });\n \n // 파일 저장\n if (this.config.type === 'file') {\n await this.saveToFile();\n }\n }\n\n return removedCount;\n }\n\n /**\n * 저장소 통계 조회\n */\n getStorageStats(): {\n totalEvents: number;\n memoryUsage: number;\n indexSizes: {\n byType: number;\n byDate: number;\n byProvider: number;\n byChannel: number;\n };\n } {\n const memoryUsage = this.estimateMemoryUsage();\n \n return {\n totalEvents: this.events.size,\n memoryUsage,\n indexSizes: {\n byType: this.indexByType.size,\n byDate: this.indexByDate.size,\n byProvider: this.indexByProvider.size,\n byChannel: this.indexByChannel.size\n }\n };\n }\n\n /**\n * 인덱스 초기화\n */\n private initializeIndexes(): void {\n // 이벤트 타입별 인덱스 초기화\n const eventTypes = Object.values(WebhookEventType);\n for (const eventType of eventTypes) {\n this.indexByType.set(eventType, new Set());\n }\n }\n\n /**\n * 인덱스에 이벤트 추가\n */\n private addToIndexes(event: WebhookEvent): void {\n // 타입 인덱스\n const typeSet = this.indexByType.get(event.type);\n if (typeSet) {\n typeSet.add(event.id);\n }\n\n // 날짜 인덱스\n const dateKey = event.timestamp.toISOString().split('T')[0];\n if (!this.indexByDate.has(dateKey)) {\n this.indexByDate.set(dateKey, new Set());\n }\n this.indexByDate.get(dateKey)!.add(event.id);\n\n // 프로바이더 인덱스\n if (event.metadata.providerId) {\n if (!this.indexByProvider.has(event.metadata.providerId)) {\n this.indexByProvider.set(event.metadata.providerId, new Set());\n }\n this.indexByProvider.get(event.metadata.providerId)!.add(event.id);\n }\n\n // 채널 인덱스\n if (event.metadata.channelId) {\n if (!this.indexByChannel.has(event.metadata.channelId)) {\n this.indexByChannel.set(event.metadata.channelId, new Set());\n }\n this.indexByChannel.get(event.metadata.channelId)!.add(event.id);\n }\n }\n\n /**\n * 인덱스에서 이벤트 제거\n */\n private removeFromIndexes(event: WebhookEvent): void {\n // 타입 인덱스\n const typeSet = this.indexByType.get(event.type);\n if (typeSet) {\n typeSet.delete(event.id);\n }\n\n // 날짜 인덱스\n const dateKey = event.timestamp.toISOString().split('T')[0];\n const dateSet = this.indexByDate.get(dateKey);\n if (dateSet) {\n dateSet.delete(event.id);\n if (dateSet.size === 0) {\n this.indexByDate.delete(dateKey);\n }\n }\n\n // 프로바이더 인덱스\n if (event.metadata.providerId) {\n const providerSet = this.indexByProvider.get(event.metadata.providerId);\n if (providerSet) {\n providerSet.delete(event.id);\n if (providerSet.size === 0) {\n this.indexByProvider.delete(event.metadata.providerId);\n }\n }\n }\n\n // 채널 인덱스\n if (event.metadata.channelId) {\n const channelSet = this.indexByChannel.get(event.metadata.channelId);\n if (channelSet) {\n channelSet.delete(event.id);\n if (channelSet.size === 0) {\n this.indexByChannel.delete(event.metadata.channelId);\n }\n }\n }\n }\n\n /**\n * 날짜 범위로 이벤트 ID 조회\n */\n private getEventIdsByDateRange(startDate?: Date, endDate?: Date): Set<string> {\n const ids = new Set<string>();\n \n for (const [dateKey, eventIds] of this.indexByDate.entries()) {\n const date = new Date(dateKey);\n \n if (startDate && date < startDate) continue;\n if (endDate && date > endDate) continue;\n \n eventIds.forEach(id => ids.add(id));\n }\n \n return ids;\n }\n\n /**\n * 필터 조건 매칭 확인\n */\n private matchesFilter(event: WebhookEvent, filter: EventFilter): boolean {\n // 템플릿 ID 필터\n if (filter.templateId && filter.templateId.length > 0) {\n if (!event.metadata.templateId || !filter.templateId.includes(event.metadata.templateId)) {\n return false;\n }\n }\n\n // 메시지 ID 필터\n if (filter.messageId && filter.messageId.length > 0) {\n if (!event.metadata.messageId || !filter.messageId.includes(event.metadata.messageId)) {\n return false;\n }\n }\n\n // 사용자 ID 필터\n if (filter.userId && filter.userId.length > 0) {\n if (!event.metadata.userId || !filter.userId.includes(event.metadata.userId)) {\n return false;\n }\n }\n\n // 조직 ID 필터\n if (filter.organizationId && filter.organizationId.length > 0) {\n if (!event.metadata.organizationId || !filter.organizationId.includes(event.metadata.organizationId)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * 객체 필드 값 가져오기\n */\n private getFieldValue(obj: any, fieldPath: string): any {\n return fieldPath.split('.').reduce((value, key) => value?.[key], obj);\n }\n\n /**\n * 메모리 사용량 추정\n */\n private estimateMemoryUsage(): number {\n let totalSize = 0;\n \n for (const event of this.events.values()) {\n // 대략적인 객체 크기 계산\n totalSize += JSON.stringify(event).length * 2; // UTF-16 기준\n }\n \n return totalSize;\n }\n\n /**\n * 메모리 사용량 확인 및 정리\n */\n private async checkMemoryUsage(): Promise<void> {\n if (!this.config.maxMemoryUsage) return;\n\n const currentUsage = this.estimateMemoryUsage();\n \n if (currentUsage > this.config.maxMemoryUsage) {\n // 오래된 이벤트부터 제거\n const events = Array.from(this.events.values())\n .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());\n \n let removedCount = 0;\n const targetUsage = this.config.maxMemoryUsage * 0.8; // 80%까지 줄이기\n \n for (const event of events) {\n if (this.estimateMemoryUsage() <= targetUsage) break;\n \n this.removeFromIndexes(event);\n this.events.delete(event.id);\n removedCount++;\n }\n \n if (removedCount > 0) {\n this.emit('memoryCleanup', { \n removedCount, \n previousUsage: currentUsage,\n currentUsage: this.estimateMemoryUsage() \n });\n }\n }\n }\n\n /**\n * 이벤트 내용 키 생성 (중복 검사용)\n */\n private generateContentKey(event: WebhookEvent): string {\n return `${event.type}_${event.metadata.messageId || ''}_${event.metadata.templateId || ''}_${JSON.stringify(event.data)}`;\n }\n\n /**\n * 정리 작업 시작\n */\n private startCleanupTask(): void {\n // 1시간마다 정리 작업 실행\n this.cleanupInterval = setInterval(async () => {\n try {\n await this.cleanupOldEvents();\n await this.cleanupDuplicateEvents();\n } catch (error) {\n this.emit('cleanupError', error);\n }\n }, 60 * 60 * 1000);\n }\n\n /**\n * 파일에 이벤트 추가\n */\n private async appendToFile(event: WebhookEvent): Promise<void> {\n if (!this.config.filePath) return;\n\n try {\n const line = JSON.stringify(event) + '\\n';\n await fs.appendFile(this.config.filePath, line, 'utf8');\n } catch (error) {\n this.emit('appendError', error);\n }\n }\n\n /**\n * 파일에서 데이터 로드\n */\n private async loadFromFile(): Promise<void> {\n if (!this.config.filePath) return;\n\n try {\n const data = await fs.readFile(this.config.filePath, 'utf8');\n const lines = data.trim().split('\\n').filter(line => line.trim());\n \n for (const line of lines) {\n try {\n const eventData = JSON.parse(line);\n const event: WebhookEvent = {\n ...eventData,\n timestamp: new Date(eventData.timestamp)\n };\n \n this.events.set(event.id, event);\n this.addToIndexes(event);\n } catch (parseError) {\n this.emit('parseError', { line, error: parseError });\n }\n }\n\n this.emit('dataLoaded', { \n filePath: this.config.filePath,\n eventCount: this.events.size \n });\n\n } catch (error) {\n if ((error as any).code !== 'ENOENT') {\n this.emit('loadError', error);\n }\n }\n }\n\n /**\n * 파일에 데이터 저장\n */\n private async saveToFile(): Promise<void> {\n if (!this.config.filePath) return;\n\n try {\n const lines = Array.from(this.events.values())\n .map(event => JSON.stringify(event))\n .join('\\n');\n \n // 디렉토리 생성\n await fs.mkdir(path.dirname(this.config.filePath), { recursive: true });\n \n // 파일 저장\n await fs.writeFile(this.config.filePath, lines + '\\n', 'utf8');\n\n this.emit('dataSaved', { \n filePath: this.config.filePath,\n eventCount: this.events.size \n });\n\n } catch (error) {\n this.emit('saveError', error);\n throw error;\n }\n }\n\n /**\n * 이벤트 저장소 종료\n */\n async shutdown(): Promise<void> {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n\n // 마지막 저장\n if (this.config.type === 'file') {\n await this.saveToFile().catch(error => {\n this.emit('saveError', error);\n });\n }\n\n this.emit('shutdown', { eventCount: this.events.size });\n }\n}"],"mappings":"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","names":["WebhookEventType","import_events","import_events","import_events","fs","path","import_events","fs","path","comparison","import_events","fs","path","comparison"]}
|