@k-msg/messaging
Version:
AlimTalk messaging core for sending, queuing, and tracking messages
855 lines (842 loc) • 24.6 kB
TypeScript
import { z } from 'zod';
import { EventEmitter } from 'events';
interface MessageRequest {
templateId: string;
recipients: Recipient[];
variables: VariableMap;
scheduling?: SchedulingOptions;
options?: SendingOptions;
}
interface Recipient {
phoneNumber: string;
variables?: VariableMap;
metadata?: Record<string, any>;
}
interface VariableMap {
[key: string]: string | number | Date;
}
interface SchedulingOptions {
scheduledAt: Date;
timezone?: string;
retryCount?: number;
}
interface SendingOptions {
priority?: 'high' | 'normal' | 'low';
ttl?: number;
failover?: {
enabled: boolean;
fallbackChannel?: 'sms' | 'lms';
fallbackContent?: string;
};
deduplication?: {
enabled: boolean;
window: number;
};
tracking?: {
enabled: boolean;
webhookUrl?: string;
};
}
interface MessageResult {
requestId: string;
results: RecipientResult[];
summary: {
total: number;
queued: number;
sent: number;
failed: number;
};
metadata: {
createdAt: Date;
provider: string;
templateId: string;
};
}
interface RecipientResult {
phoneNumber: string;
messageId?: string;
status: MessageStatus;
error?: MessageError;
metadata?: Record<string, any>;
}
declare enum MessageStatus {
QUEUED = "QUEUED",// 큐에 대기 중
SENDING = "SENDING",// 발송 중
SENT = "SENT",// 발송 완료
DELIVERED = "DELIVERED",// 전달 완료
FAILED = "FAILED",// 발송 실패
CLICKED = "CLICKED",// 버튼 클릭됨
CANCELLED = "CANCELLED"
}
interface MessageError {
code: string;
message: string;
details?: Record<string, any>;
}
interface DeliveryReport {
messageId: string;
phoneNumber: string;
status: MessageStatus;
sentAt?: Date;
deliveredAt?: Date;
clickedAt?: Date;
failedAt?: Date;
error?: MessageError;
attempts: DeliveryAttempt[];
metadata: Record<string, any>;
}
interface DeliveryAttempt {
attemptNumber: number;
attemptedAt: Date;
status: MessageStatus;
error?: MessageError;
provider: string;
}
interface BulkMessageRequest {
templateId: string;
recipients: BulkRecipient[];
commonVariables?: VariableMap;
options?: BulkSendingOptions;
}
interface BulkRecipient {
phoneNumber: string;
variables: VariableMap;
metadata?: Record<string, any>;
}
interface BulkSendingOptions extends SendingOptions {
batchSize?: number;
batchDelay?: number;
maxConcurrency?: number;
}
interface BulkMessageResult {
requestId: string;
totalRecipients: number;
batches: BulkBatchResult[];
summary: {
queued: number;
sent: number;
failed: number;
processing: number;
};
createdAt: Date;
completedAt?: Date;
}
interface BulkBatchResult {
batchId: string;
batchNumber: number;
recipients: RecipientResult[];
status: 'pending' | 'processing' | 'completed' | 'failed';
createdAt: Date;
completedAt?: Date;
}
declare enum MessageEventType {
TEMPLATE_CREATED = "template.created",
TEMPLATE_APPROVED = "template.approved",
TEMPLATE_REJECTED = "template.rejected",
TEMPLATE_UPDATED = "template.updated",
TEMPLATE_DELETED = "template.deleted",
MESSAGE_QUEUED = "message.queued",
MESSAGE_SENT = "message.sent",
MESSAGE_DELIVERED = "message.delivered",
MESSAGE_FAILED = "message.failed",
MESSAGE_CLICKED = "message.clicked",
MESSAGE_CANCELLED = "message.cancelled",
CHANNEL_CREATED = "channel.created",
CHANNEL_VERIFIED = "channel.verified",
SENDER_NUMBER_ADDED = "sender_number.added",
QUOTA_WARNING = "system.quota_warning",
QUOTA_EXCEEDED = "system.quota_exceeded",
PROVIDER_ERROR = "system.provider_error"
}
interface MessageEvent<T = any> {
id: string;
type: MessageEventType;
timestamp: Date;
data: T;
metadata: {
providerId?: string;
userId?: string;
organizationId?: string;
correlationId?: string;
};
}
declare const VariableMapSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>>;
declare const RecipientSchema: z.ZodObject<{
phoneNumber: z.ZodString;
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>>>;
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, z.core.$strip>;
declare const SchedulingOptionsSchema: z.ZodObject<{
scheduledAt: z.ZodDate;
timezone: z.ZodOptional<z.ZodString>;
retryCount: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
}, z.core.$strip>;
declare const SendingOptionsSchema: z.ZodObject<{
priority: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
high: "high";
normal: "normal";
low: "low";
}>>>;
ttl: z.ZodOptional<z.ZodNumber>;
failover: z.ZodOptional<z.ZodObject<{
enabled: z.ZodBoolean;
fallbackChannel: z.ZodOptional<z.ZodEnum<{
sms: "sms";
lms: "lms";
}>>;
fallbackContent: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>;
deduplication: z.ZodOptional<z.ZodObject<{
enabled: z.ZodBoolean;
window: z.ZodNumber;
}, z.core.$strip>>;
tracking: z.ZodOptional<z.ZodObject<{
enabled: z.ZodBoolean;
webhookUrl: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>;
}, z.core.$strip>;
declare const MessageRequestSchema: z.ZodObject<{
templateId: z.ZodString;
recipients: z.ZodArray<z.ZodObject<{
phoneNumber: z.ZodString;
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>>>;
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, z.core.$strip>>;
variables: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>>;
scheduling: z.ZodOptional<z.ZodObject<{
scheduledAt: z.ZodDate;
timezone: z.ZodOptional<z.ZodString>;
retryCount: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
}, z.core.$strip>>;
options: z.ZodOptional<z.ZodObject<{
priority: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
high: "high";
normal: "normal";
low: "low";
}>>>;
ttl: z.ZodOptional<z.ZodNumber>;
failover: z.ZodOptional<z.ZodObject<{
enabled: z.ZodBoolean;
fallbackChannel: z.ZodOptional<z.ZodEnum<{
sms: "sms";
lms: "lms";
}>>;
fallbackContent: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>;
deduplication: z.ZodOptional<z.ZodObject<{
enabled: z.ZodBoolean;
window: z.ZodNumber;
}, z.core.$strip>>;
tracking: z.ZodOptional<z.ZodObject<{
enabled: z.ZodBoolean;
webhookUrl: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>;
}, z.core.$strip>>;
}, z.core.$strip>;
declare const MessageErrorSchema: z.ZodObject<{
code: z.ZodString;
message: z.ZodString;
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, z.core.$strip>;
declare const RecipientResultSchema: z.ZodObject<{
phoneNumber: z.ZodString;
messageId: z.ZodOptional<z.ZodString>;
status: z.ZodEnum<typeof MessageStatus>;
error: z.ZodOptional<z.ZodObject<{
code: z.ZodString;
message: z.ZodString;
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, z.core.$strip>>;
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, z.core.$strip>;
declare const MessageResultSchema: z.ZodObject<{
requestId: z.ZodString;
results: z.ZodArray<z.ZodObject<{
phoneNumber: z.ZodString;
messageId: z.ZodOptional<z.ZodString>;
status: z.ZodEnum<typeof MessageStatus>;
error: z.ZodOptional<z.ZodObject<{
code: z.ZodString;
message: z.ZodString;
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, z.core.$strip>>;
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, z.core.$strip>>;
summary: z.ZodObject<{
total: z.ZodNumber;
queued: z.ZodNumber;
sent: z.ZodNumber;
failed: z.ZodNumber;
}, z.core.$strip>;
metadata: z.ZodObject<{
createdAt: z.ZodDate;
provider: z.ZodString;
templateId: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
type MessageRequestType = z.infer<typeof MessageRequestSchema>;
type RecipientType = z.infer<typeof RecipientSchema>;
type MessageResultType = z.infer<typeof MessageResultSchema>;
interface Provider {
id: string;
name: string;
send(request: ProviderMessageRequest): Promise<ProviderMessageResult>;
}
interface ProviderMessageRequest {
templateCode: string;
phoneNumber: string;
variables: Record<string, any>;
options?: Record<string, any>;
}
interface ProviderMessageResult {
messageId: string;
status: MessageStatus;
error?: {
code: string;
message: string;
};
}
declare class SingleMessageSender {
private providers;
private templates;
addProvider(provider: Provider): void;
removeProvider(providerId: string): void;
send(request: MessageRequest): Promise<MessageResult>;
private sendToRecipient;
private getTemplate;
private calculateSummary;
private generateRequestId;
cancelMessage(messageId: string): Promise<boolean>;
getMessageStatus(messageId: string): Promise<MessageStatus>;
resendMessage(messageId: string, options?: {
newRecipient?: string;
}): Promise<MessageResult>;
}
declare class BulkMessageSender {
private singleSender;
private activeBulkJobs;
constructor(singleSender: SingleMessageSender);
sendBulk(request: BulkMessageRequest): Promise<BulkMessageResult>;
private processBatchesAsync;
private processBatch;
private processRecipient;
private createBatches;
private delay;
private generateRequestId;
getBulkStatus(requestId: string): Promise<BulkMessageResult | null>;
cancelBulkJob(requestId: string): Promise<boolean>;
retryFailedBatch(requestId: string, batchId: string): Promise<BulkBatchResult | null>;
cleanup(): void;
}
/**
* Job processor for message queue system
*/
interface Job<T = any> {
id: string;
type: string;
data: T;
priority: number;
attempts: number;
maxAttempts: number;
delay: number;
createdAt: Date;
processAt: Date;
completedAt?: Date;
failedAt?: Date;
error?: string;
metadata: Record<string, any>;
}
interface JobProcessorOptions {
concurrency: number;
retryDelays: number[];
maxRetries: number;
pollInterval: number;
enableMetrics: boolean;
rateLimiter?: {
maxRequests: number;
windowMs: number;
};
circuitBreaker?: {
failureThreshold: number;
timeout: number;
resetTimeout: number;
};
}
interface JobHandler<T = any> {
(job: Job<T>): Promise<any>;
}
interface JobProcessorMetrics {
processed: number;
succeeded: number;
failed: number;
retried: number;
activeJobs: number;
queueSize: number;
averageProcessingTime: number;
lastProcessedAt?: Date;
}
declare class JobProcessor extends EventEmitter {
private options;
private handlers;
private queue;
private processing;
private isRunning;
private pollTimer?;
private metrics;
private rateLimiter?;
private circuitBreaker?;
constructor(options: JobProcessorOptions);
/**
* Register a job handler
*/
handle<T>(jobType: string, handler: JobHandler<T>): void;
/**
* Add a job to the queue
*/
add<T>(jobType: string, data: T, options?: {
priority?: number;
delay?: number;
maxAttempts?: number;
metadata?: Record<string, any>;
}): Promise<string>;
/**
* Start processing jobs
*/
start(): void;
/**
* Stop processing jobs
*/
stop(): Promise<void>;
/**
* Get current metrics
*/
getMetrics(): JobProcessorMetrics;
/**
* Get queue status
*/
getQueueStatus(): {
pending: number;
processing: number;
failed: number;
totalProcessed: number;
};
/**
* Remove completed jobs from queue
*/
cleanup(): number;
/**
* Get specific job by ID
*/
getJob(jobId: string): Job | undefined;
/**
* Remove job from queue
*/
removeJob(jobId: string): boolean;
private scheduleNextPoll;
private processJobs;
private processJob;
private failJob;
private getRetryDelay;
private updateMetrics;
private updateAverageProcessingTime;
}
/**
* Specific processor for message jobs
*/
declare class MessageJobProcessor extends JobProcessor {
constructor(options?: Partial<JobProcessorOptions>);
private setupMessageHandlers;
private processSingleMessage;
private processBulkMessages;
private processDeliveryUpdate;
private processScheduledMessage;
/**
* Add a message to the processing queue
*/
queueMessage(messageRequest: MessageRequest, options?: {
priority?: number;
delay?: number;
metadata?: Record<string, any>;
}): Promise<string>;
/**
* Add bulk messages to the processing queue
*/
queueBulkMessages(messageRequests: MessageRequest[], options?: {
priority?: number;
delay?: number;
metadata?: Record<string, any>;
}): Promise<string>;
/**
* Schedule a message for future delivery
*/
scheduleMessage(messageRequest: MessageRequest, scheduledAt: Date, options?: {
metadata?: Record<string, any>;
}): Promise<string>;
}
/**
* Retry handler for failed message deliveries
*/
interface RetryPolicy {
maxAttempts: number;
backoffMultiplier: number;
initialDelay: number;
maxDelay: number;
jitter: boolean;
retryableStatuses: MessageStatus[];
retryableErrorCodes: string[];
}
interface RetryAttempt {
messageId: string;
phoneNumber: string;
attemptNumber: number;
scheduledAt: Date;
provider: string;
templateId: string;
variables: Record<string, any>;
metadata: Record<string, any>;
}
interface RetryQueueItem {
id: string;
messageId: string;
phoneNumber: string;
originalDeliveryReport: DeliveryReport;
attempts: RetryAttempt[];
nextRetryAt: Date;
status: 'pending' | 'processing' | 'exhausted' | 'cancelled';
createdAt: Date;
updatedAt: Date;
}
interface RetryHandlerOptions {
policy: RetryPolicy;
checkInterval: number;
maxQueueSize: number;
enablePersistence: boolean;
onRetryExhausted?: (item: RetryQueueItem) => Promise<void>;
onRetrySuccess?: (item: RetryQueueItem, result: any) => Promise<void>;
onRetryFailed?: (item: RetryQueueItem, error: Error) => Promise<void>;
}
interface RetryHandlerMetrics {
totalRetries: number;
successfulRetries: number;
failedRetries: number;
exhaustedRetries: number;
queueSize: number;
averageRetryDelay: number;
lastRetryAt?: Date;
}
declare class MessageRetryHandler extends EventEmitter {
private options;
private retryQueue;
private processing;
private checkTimer?;
private isRunning;
private metrics;
private defaultPolicy;
constructor(options: RetryHandlerOptions);
/**
* Start the retry handler
*/
start(): void;
/**
* Stop the retry handler
*/
stop(): Promise<void>;
/**
* Add a failed delivery for retry
*/
addForRetry(deliveryReport: DeliveryReport): Promise<boolean>;
/**
* Cancel retry for a specific message
*/
cancelRetry(messageId: string): boolean;
/**
* Get retry status for a message
*/
getRetryStatus(messageId: string): RetryQueueItem | undefined;
/**
* Get all retry queue items
*/
getRetryQueue(): RetryQueueItem[];
/**
* Get metrics
*/
getMetrics(): RetryHandlerMetrics;
/**
* Clean up completed/exhausted retry items
*/
cleanup(): number;
private scheduleNextCheck;
private processRetryQueue;
private processRetryItem;
private executeRetry;
private shouldRetry;
private createRetryItem;
private updateRetryItem;
private calculateRetryDelay;
private cleanupQueue;
private updateMetrics;
}
/**
* Delivery tracking system for messages
*/
interface DeliveryTrackingOptions {
trackingInterval: number;
maxTrackingDuration: number;
batchSize: number;
enableWebhooks: boolean;
webhookRetries: number;
webhookTimeout: number;
persistence: {
enabled: boolean;
retentionDays: number;
};
}
interface DeliveryWebhook {
url: string;
events: MessageEventType[];
secret?: string;
headers?: Record<string, string>;
timeout: number;
retries: number;
}
interface TrackingRecord {
messageId: string;
phoneNumber: string;
templateId: string;
provider: string;
currentStatus: MessageStatus;
statusHistory: StatusHistoryEntry[];
deliveryReport: DeliveryReport;
webhooks: DeliveryWebhook[];
createdAt: Date;
updatedAt: Date;
expiresAt: Date;
metadata: Record<string, any>;
}
interface StatusHistoryEntry {
status: MessageStatus;
timestamp: Date;
provider: string;
details?: Record<string, any>;
source: 'provider' | 'webhook' | 'manual' | 'system';
}
interface DeliveryStats {
totalMessages: number;
byStatus: Record<MessageStatus, number>;
byProvider: Record<string, number>;
averageDeliveryTime: number;
deliveryRate: number;
failureRate: number;
lastUpdated: Date;
}
declare class DeliveryTracker extends EventEmitter {
private options;
private trackingRecords;
private statusIndex;
private trackingTimer?;
private webhookQueue;
private isRunning;
private stats;
private defaultOptions;
constructor(options: DeliveryTrackingOptions);
/**
* Start delivery tracking
*/
start(): void;
/**
* Stop delivery tracking
*/
stop(): void;
/**
* Start tracking a message
*/
trackMessage(messageId: string, phoneNumber: string, templateId: string, provider: string, options?: {
webhooks?: DeliveryWebhook[];
metadata?: Record<string, any>;
initialStatus?: MessageStatus;
}): Promise<void>;
/**
* Update message status
*/
updateStatus(messageId: string, status: MessageStatus, details?: {
provider?: string;
error?: {
code: string;
message: string;
details?: any;
};
metadata?: Record<string, any>;
source?: 'provider' | 'webhook' | 'manual' | 'system';
sentAt?: Date;
deliveredAt?: Date;
clickedAt?: Date;
failedAt?: Date;
}): Promise<boolean>;
/**
* Get delivery report for a message
*/
getDeliveryReport(messageId: string): DeliveryReport | undefined;
/**
* Get tracking record for a message
*/
getTrackingRecord(messageId: string): TrackingRecord | undefined;
/**
* Get messages by status
*/
getMessagesByStatus(status: MessageStatus): TrackingRecord[];
/**
* Get delivery statistics
*/
getStats(): DeliveryStats;
/**
* Get delivery statistics for a specific time range
*/
getStatsForPeriod(startDate: Date, endDate: Date): DeliveryStats;
/**
* Clean up expired tracking records
*/
cleanup(): number;
/**
* Stop tracking a specific message
*/
stopTracking(messageId: string): boolean;
private scheduleTracking;
private processTracking;
private processWebhookQueue;
private deliverWebhook;
private sendWebhook;
private queueWebhook;
private isStatusProgression;
private isTerminalStatus;
private getEventTypeForStatus;
private updateStats;
}
/**
* Variable replacement and personalization system
*/
interface VariableReplacementOptions {
variablePattern: RegExp;
allowUndefined: boolean;
undefinedReplacement: string;
caseSensitive: boolean;
enableFormatting: boolean;
enableConditionals: boolean;
enableLoops: boolean;
maxRecursionDepth: number;
}
interface VariableInfo {
name: string;
value: any;
formatted: string;
type: 'string' | 'number' | 'date' | 'boolean' | 'array' | 'object' | 'undefined';
position: {
start: number;
end: number;
};
}
interface ReplacementResult {
content: string;
variables: VariableInfo[];
missingVariables: string[];
errors: ReplacementError[];
metadata: {
originalLength: number;
finalLength: number;
variableCount: number;
replacementTime: number;
};
}
interface ReplacementError {
type: 'missing_variable' | 'format_error' | 'syntax_error' | 'recursion_limit';
message: string;
variable?: string;
position?: {
start: number;
end: number;
};
}
declare class VariableReplacer {
private options;
private defaultOptions;
constructor(options?: Partial<VariableReplacementOptions>);
/**
* Replace variables in content
*/
replace(content: string, variables: VariableMap): ReplacementResult;
/**
* Extract variables from content without replacing
*/
extractVariables(content: string): string[];
/**
* Validate that all required variables are provided
*/
validate(content: string, variables: VariableMap): {
isValid: boolean;
missingVariables: string[];
errors: ReplacementError[];
};
/**
* Preview replacement result without actually replacing
*/
preview(content: string, variables: VariableMap): {
originalContent: string;
previewContent: string;
variableHighlights: Array<{
variable: string;
value: string;
positions: Array<{
start: number;
end: number;
}>;
}>;
};
private replaceSimpleVariables;
private replaceRecursive;
private processConditionals;
private processLoops;
private parseVariableName;
private getVariableValue;
private formatValue;
private formatDate;
private getValueType;
private hasVariables;
private extractConditionals;
private extractLoops;
private extractVariablesFromExpression;
private evaluateCondition;
private buildConditionalPattern;
private buildLoopPattern;
}
/**
* Default instance with Korean-optimized settings
*/
declare const defaultVariableReplacer: VariableReplacer;
/**
* Utility functions
*/
declare const VariableUtils: {
/**
* Extract all variables from content
*/
extractVariables: (content: string) => string[];
/**
* Replace variables in content
*/
replace: (content: string, variables: VariableMap) => string;
/**
* Validate content has all required variables
*/
validate: (content: string, variables: VariableMap) => boolean;
/**
* Create personalized content for multiple recipients
*/
personalize: (content: string, recipients: Array<{
phoneNumber: string;
variables: VariableMap;
}>) => Array<{
phoneNumber: string;
content: string;
errors?: string[];
}>;
};
export { type BulkBatchResult, type BulkMessageRequest, type BulkMessageResult, BulkMessageSender, type BulkRecipient, type BulkSendingOptions, type DeliveryAttempt, type DeliveryReport, DeliveryTracker, JobProcessor, type MessageError, MessageErrorSchema, type MessageEvent, MessageEventType, MessageJobProcessor, type MessageRequest, MessageRequestSchema, type MessageRequestType, type MessageResult, MessageResultSchema, type MessageResultType, MessageRetryHandler, MessageStatus, type Provider, type ProviderMessageRequest, type ProviderMessageResult, type Recipient, type RecipientResult, RecipientResultSchema, RecipientSchema, type RecipientType, type SchedulingOptions, SchedulingOptionsSchema, type SendingOptions, SendingOptionsSchema, SingleMessageSender, type VariableMap, VariableMapSchema, VariableReplacer, VariableUtils, defaultVariableReplacer };