UNPKG

@k-msg/core

Version:

Core types and interfaces for K-Message platform

1,015 lines (1,010 loc) 33.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { BulkOperationHandler: () => BulkOperationHandler, CircuitBreaker: () => CircuitBreaker, ErrorFactory: () => ErrorFactory, ErrorRecovery: () => ErrorRecovery, ErrorUtils: () => ErrorUtils, GracefulDegradation: () => GracefulDegradation, HealthMonitor: () => HealthMonitor, KMessageError: () => KMessageError, KMessageErrorCode: () => KMessageErrorCode, MessageError: () => MessageError, MockProvider: () => MockProvider, PerformanceTest: () => PerformanceTest, ProviderError: () => ProviderError, RateLimiter: () => RateLimiter, Result: () => Result, RetryHandler: () => RetryHandler, TemplateCategory: () => TemplateCategory, TemplateError: () => TemplateError, TestAssertions: () => TestAssertions, TestData: () => TestData, TestSetup: () => TestSetup }); module.exports = __toCommonJS(index_exports); // src/errors.ts var KMessageErrorCode = /* @__PURE__ */ ((KMessageErrorCode2) => { KMessageErrorCode2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR"; KMessageErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR"; KMessageErrorCode2["CONFIGURATION_ERROR"] = "CONFIGURATION_ERROR"; KMessageErrorCode2["PROVIDER_NOT_FOUND"] = "PROVIDER_NOT_FOUND"; KMessageErrorCode2["PROVIDER_NOT_AVAILABLE"] = "PROVIDER_NOT_AVAILABLE"; KMessageErrorCode2["PROVIDER_AUTHENTICATION_FAILED"] = "PROVIDER_AUTHENTICATION_FAILED"; KMessageErrorCode2["PROVIDER_CONNECTION_FAILED"] = "PROVIDER_CONNECTION_FAILED"; KMessageErrorCode2["PROVIDER_RATE_LIMITED"] = "PROVIDER_RATE_LIMITED"; KMessageErrorCode2["PROVIDER_INSUFFICIENT_BALANCE"] = "PROVIDER_INSUFFICIENT_BALANCE"; KMessageErrorCode2["TEMPLATE_NOT_FOUND"] = "TEMPLATE_NOT_FOUND"; KMessageErrorCode2["TEMPLATE_VALIDATION_FAILED"] = "TEMPLATE_VALIDATION_FAILED"; KMessageErrorCode2["TEMPLATE_CREATION_FAILED"] = "TEMPLATE_CREATION_FAILED"; KMessageErrorCode2["TEMPLATE_MODIFICATION_FAILED"] = "TEMPLATE_MODIFICATION_FAILED"; KMessageErrorCode2["TEMPLATE_DELETION_FAILED"] = "TEMPLATE_DELETION_FAILED"; KMessageErrorCode2["MESSAGE_SEND_FAILED"] = "MESSAGE_SEND_FAILED"; KMessageErrorCode2["MESSAGE_INVALID_PHONE_NUMBER"] = "MESSAGE_INVALID_PHONE_NUMBER"; KMessageErrorCode2["MESSAGE_INVALID_VARIABLES"] = "MESSAGE_INVALID_VARIABLES"; KMessageErrorCode2["MESSAGE_QUOTA_EXCEEDED"] = "MESSAGE_QUOTA_EXCEEDED"; KMessageErrorCode2["MESSAGE_RESERVATION_FAILED"] = "MESSAGE_RESERVATION_FAILED"; KMessageErrorCode2["MESSAGE_CANCELLATION_FAILED"] = "MESSAGE_CANCELLATION_FAILED"; KMessageErrorCode2["NETWORK_TIMEOUT"] = "NETWORK_TIMEOUT"; KMessageErrorCode2["NETWORK_CONNECTION_FAILED"] = "NETWORK_CONNECTION_FAILED"; KMessageErrorCode2["NETWORK_SERVICE_UNAVAILABLE"] = "NETWORK_SERVICE_UNAVAILABLE"; KMessageErrorCode2["API_INVALID_REQUEST"] = "API_INVALID_REQUEST"; KMessageErrorCode2["API_UNAUTHORIZED"] = "API_UNAUTHORIZED"; KMessageErrorCode2["API_FORBIDDEN"] = "API_FORBIDDEN"; KMessageErrorCode2["API_NOT_FOUND"] = "API_NOT_FOUND"; KMessageErrorCode2["API_TOO_MANY_REQUESTS"] = "API_TOO_MANY_REQUESTS"; KMessageErrorCode2["API_INTERNAL_SERVER_ERROR"] = "API_INTERNAL_SERVER_ERROR"; return KMessageErrorCode2; })(KMessageErrorCode || {}); var KMessageError = class _KMessageError extends Error { code; context; retryable; statusCode; cause; constructor(code, message, context = {}, options = {}) { super(message); this.name = "KMessageError"; this.code = code; this.context = { ...context, timestamp: context.timestamp || /* @__PURE__ */ new Date() }; this.retryable = options.retryable ?? this.isRetryableByDefault(code); this.statusCode = options.statusCode; this.cause = options.cause; if (Error.captureStackTrace) { Error.captureStackTrace(this, _KMessageError); } } isRetryableByDefault(code) { const retryableCodes = [ "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */, "NETWORK_CONNECTION_FAILED" /* NETWORK_CONNECTION_FAILED */, "NETWORK_SERVICE_UNAVAILABLE" /* NETWORK_SERVICE_UNAVAILABLE */, "PROVIDER_CONNECTION_FAILED" /* PROVIDER_CONNECTION_FAILED */, "PROVIDER_RATE_LIMITED" /* PROVIDER_RATE_LIMITED */, "API_TOO_MANY_REQUESTS" /* API_TOO_MANY_REQUESTS */, "API_INTERNAL_SERVER_ERROR" /* API_INTERNAL_SERVER_ERROR */ ]; return retryableCodes.includes(code); } toJSON() { return { name: this.name, code: this.code, message: this.message, context: this.context, retryable: this.retryable, statusCode: this.statusCode, stack: this.stack }; } }; var ProviderError = class extends KMessageError { constructor(providerId, code, message, context = {}, options = {}) { super(code, message, { ...context, providerId }, options); this.name = "ProviderError"; } }; var TemplateError = class extends KMessageError { constructor(templateCode, code, message, context = {}, options = {}) { super(code, message, { ...context, templateCode }, options); this.name = "TemplateError"; } }; var MessageError = class extends KMessageError { constructor(phoneNumber, code, message, context = {}, options = {}) { super(code, message, { ...context, phoneNumber }, options); this.name = "MessageError"; } }; var ErrorFactory = { providerNotFound: (providerId) => new ProviderError( providerId, "PROVIDER_NOT_FOUND" /* PROVIDER_NOT_FOUND */, `Provider '${providerId}' not found`, {}, { retryable: false, statusCode: 404 } ), providerNotAvailable: (providerId, reason) => new ProviderError( providerId, "PROVIDER_NOT_AVAILABLE" /* PROVIDER_NOT_AVAILABLE */, `Provider '${providerId}' is not available${reason ? `: ${reason}` : ""}`, {}, { retryable: true, statusCode: 503 } ), authenticationFailed: (providerId, details) => new ProviderError( providerId, "PROVIDER_AUTHENTICATION_FAILED" /* PROVIDER_AUTHENTICATION_FAILED */, `Authentication failed for provider '${providerId}'${details ? `: ${details}` : ""}`, {}, { retryable: false, statusCode: 401 } ), templateNotFound: (templateCode) => new TemplateError( templateCode, "TEMPLATE_NOT_FOUND" /* TEMPLATE_NOT_FOUND */, `Template '${templateCode}' not found`, {}, { retryable: false, statusCode: 404 } ), invalidPhoneNumber: (phoneNumber) => new MessageError( phoneNumber, "MESSAGE_INVALID_PHONE_NUMBER" /* MESSAGE_INVALID_PHONE_NUMBER */, `Invalid phone number format: ${phoneNumber}`, {}, { retryable: false, statusCode: 400 } ), networkTimeout: (providerId, timeout) => new KMessageError( "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */, `Network request timed out${timeout ? ` after ${timeout}ms` : ""}`, { providerId }, { retryable: true, statusCode: 408 } ), rateLimited: (providerId, retryAfter) => new ProviderError( providerId, "PROVIDER_RATE_LIMITED" /* PROVIDER_RATE_LIMITED */, `Rate limit exceeded for provider '${providerId}'${retryAfter ? `. Retry after ${retryAfter}s` : ""}`, { retryAfter }, { retryable: true, statusCode: 429 } ), insufficientBalance: (providerId, balance) => new ProviderError( providerId, "PROVIDER_INSUFFICIENT_BALANCE" /* PROVIDER_INSUFFICIENT_BALANCE */, `Insufficient balance for provider '${providerId}'${balance ? `. Current balance: ${balance}` : ""}`, { balance }, { retryable: false, statusCode: 402 } ), fromHttpStatus: (statusCode, message, context = {}) => { let code; let retryable = false; switch (statusCode) { case 400: code = "API_INVALID_REQUEST" /* API_INVALID_REQUEST */; break; case 401: code = "API_UNAUTHORIZED" /* API_UNAUTHORIZED */; break; case 403: code = "API_FORBIDDEN" /* API_FORBIDDEN */; break; case 404: code = "API_NOT_FOUND" /* API_NOT_FOUND */; break; case 408: code = "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */; retryable = true; break; case 429: code = "API_TOO_MANY_REQUESTS" /* API_TOO_MANY_REQUESTS */; retryable = true; break; case 500: case 502: case 503: case 504: code = "API_INTERNAL_SERVER_ERROR" /* API_INTERNAL_SERVER_ERROR */; retryable = true; break; default: code = "UNKNOWN_ERROR" /* UNKNOWN_ERROR */; retryable = statusCode >= 500 && statusCode < 600; } return new KMessageError(code, message, context, { retryable, statusCode }); } }; var Result = { success: (data) => ({ success: true, data }), failure: (error) => ({ success: false, error }), fromPromise: async (promise) => { try { const data = await promise; return Result.success(data); } catch (error) { if (error instanceof KMessageError) { return Result.failure(error); } return Result.failure(new KMessageError( "UNKNOWN_ERROR" /* UNKNOWN_ERROR */, error instanceof Error ? error.message : "Unknown error occurred", {}, { cause: error instanceof Error ? error : void 0 } )); } } }; var ErrorUtils = { isRetryable: (error) => { if (error instanceof KMessageError) { return error.retryable; } const retryableMessages = ["timeout", "ECONNRESET", "ECONNREFUSED", "ETIMEDOUT"]; return retryableMessages.some( (msg) => error.message.toLowerCase().includes(msg.toLowerCase()) ); }, getStatusCode: (error) => { if (error instanceof KMessageError && error.statusCode) { return error.statusCode; } return 500; }, formatErrorForClient: (error) => { if (error instanceof KMessageError) { return { code: error.code, message: error.message, retryable: error.retryable, context: error.context }; } return { code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */, message: error.message || "Unknown error occurred", retryable: false, context: {} }; }, formatErrorForLogging: (error) => { const baseInfo = { name: error.name, message: error.message, stack: error.stack }; if (error instanceof KMessageError) { return { ...baseInfo, code: error.code, context: error.context, retryable: error.retryable, statusCode: error.statusCode }; } return baseInfo; } }; // src/test-utils.ts var import_bun_test = require("bun:test"); var MockProvider = class { id; name; _healthy = true; _issues = []; _balance = "1000"; _templates = []; _history = []; constructor(id = "mock", name = "Mock Provider") { this.id = id; this.name = name; } // Health check simulation async healthCheck() { return { healthy: this._healthy, issues: [...this._issues], data: { balance: this._balance, status: this._healthy ? "connected" : "disconnected", code: this._healthy ? 200 : 500, message: this._healthy ? "OK" : "Service unavailable" } }; } // Test helpers setHealthy(healthy, issues = []) { this._healthy = healthy; this._issues = issues; } setBalance(balance) { this._balance = balance; } setTemplates(templates) { this._templates = templates; } setHistory(history) { this._history = history; } // Provider methods with mock implementations async sendMessage(templateCode, phoneNumber, variables, options) { if (!this._healthy) { throw new MessageError(phoneNumber, "MESSAGE_SEND_FAILED" /* MESSAGE_SEND_FAILED */, "Provider is unhealthy"); } if (phoneNumber === "01000000000") { throw new MessageError(phoneNumber, "MESSAGE_INVALID_PHONE_NUMBER" /* MESSAGE_INVALID_PHONE_NUMBER */, "Invalid phone number"); } return { success: true, messageId: `mock_${Date.now()}`, status: "sent", error: null }; } async getTemplates(page = 1, size = 15, filters) { if (!this._healthy) { throw new ProviderError(this.id, "PROVIDER_NOT_AVAILABLE" /* PROVIDER_NOT_AVAILABLE */, "Provider is unhealthy"); } const start = (page - 1) * size; const end = start + size; const list = this._templates.slice(start, end); return { code: 200, message: "Success", totalCount: this._templates.length, list }; } async createTemplate(name, content, category, buttons) { if (!this._healthy) { throw new TemplateError(name, "TEMPLATE_CREATION_FAILED" /* TEMPLATE_CREATION_FAILED */, "Provider is unhealthy"); } if (name === "invalid_template") { throw new TemplateError(name, "TEMPLATE_VALIDATION_FAILED" /* TEMPLATE_VALIDATION_FAILED */, "Template validation failed"); } const templateCode = `mock_${name}_${Date.now()}`; this._templates.push({ templateCode, templateName: name, templateContent: content, status: "Y", createDate: (/* @__PURE__ */ new Date()).toISOString() }); return { success: true, templateCode, status: "created", error: null }; } async modifyTemplate(templateCode, name, content, buttons) { if (!this._healthy) { throw new TemplateError(templateCode, "TEMPLATE_MODIFICATION_FAILED" /* TEMPLATE_MODIFICATION_FAILED */, "Provider is unhealthy"); } const template = this._templates.find((t) => t.templateCode === templateCode); if (!template) { throw new TemplateError(templateCode, "TEMPLATE_NOT_FOUND" /* TEMPLATE_NOT_FOUND */, "Template not found"); } template.templateName = name; template.templateContent = content; return { success: true, templateCode, status: "modified", error: null }; } async deleteTemplate(templateCode) { if (!this._healthy) { throw new TemplateError(templateCode, "TEMPLATE_DELETION_FAILED" /* TEMPLATE_DELETION_FAILED */, "Provider is unhealthy"); } const index = this._templates.findIndex((t) => t.templateCode === templateCode); if (index === -1) { return { code: 404, message: "Template not found" }; } this._templates.splice(index, 1); return { code: 200, message: "Template deleted successfully" }; } async getHistory(page = 1, size = 15, filters) { if (!this._healthy) { throw new ProviderError(this.id, "PROVIDER_NOT_AVAILABLE" /* PROVIDER_NOT_AVAILABLE */, "Provider is unhealthy"); } const start = (page - 1) * size; const end = start + size; const list = this._history.slice(start, end); return { code: 200, message: "Success", totalCount: this._history.length, list }; } async cancelReservation(messageId) { if (!this._healthy) { throw new KMessageError("MESSAGE_CANCELLATION_FAILED" /* MESSAGE_CANCELLATION_FAILED */, "Provider is unhealthy"); } return { code: 200, message: "Reservation cancelled successfully" }; } }; var TestAssertions = { /** * Assert that an error is a KMessageError with specific code */ assertKMessageError: (error, expectedCode, expectedMessage) => { (0, import_bun_test.expect)(error).toBeInstanceOf(KMessageError); const kError = error; (0, import_bun_test.expect)(kError.code).toBe(expectedCode); if (expectedMessage) { (0, import_bun_test.expect)(kError.message).toContain(expectedMessage); } }, /** * Assert that an error is retryable */ assertRetryable: (error, expected = true) => { (0, import_bun_test.expect)(error.retryable).toBe(expected); }, /** * Assert that a health status is healthy */ assertHealthy: (health, expected = true) => { (0, import_bun_test.expect)(health.healthy).toBe(expected); if (!expected) { (0, import_bun_test.expect)(health.issues.length).toBeGreaterThan(0); } }, /** * Assert that a provider result has expected structure */ assertProviderResult: (result, expectSuccess = true) => { (0, import_bun_test.expect)(result).toHaveProperty("success"); (0, import_bun_test.expect)(result.success).toBe(expectSuccess); if (expectSuccess) { (0, import_bun_test.expect)(result.error).toBeNull(); } else { (0, import_bun_test.expect)(result.error).toBeDefined(); } }, /** * Assert that API response has expected structure */ assertApiResponse: (response, expectedCode = 200) => { (0, import_bun_test.expect)(response).toHaveProperty("code"); (0, import_bun_test.expect)(response).toHaveProperty("message"); (0, import_bun_test.expect)(response.code).toBe(expectedCode); } }; var TestData = { createMockTemplate: (overrides = {}) => ({ templateCode: "mock_template_001", templateName: "Mock Template", templateContent: "[#{\uC11C\uBE44\uC2A4\uBA85}] \uC548\uB155\uD558\uC138\uC694, #{\uACE0\uAC1D\uBA85}\uB2D8!", status: "Y", createDate: "2024-01-01 12:00:00", ...overrides }), createMockMessage: (overrides = {}) => ({ seqNo: 12345, phone: "01012345678", templateCode: "mock_template_001", statusCode: "OK", statusCodeName: "\uC131\uACF5", requestDate: "2024-01-01 12:00:00", sendDate: "2024-01-01 12:01:00", receiveDate: "2024-01-01 12:01:30", sendMessage: "[MyApp] \uC548\uB155\uD558\uC138\uC694, \uD64D\uAE38\uB3D9\uB2D8!", ...overrides }), createMockVariables: (overrides = {}) => ({ \uC11C\uBE44\uC2A4\uBA85: "MyApp", \uACE0\uAC1D\uBA85: "\uD64D\uAE38\uB3D9", \uC778\uC99D\uCF54\uB4DC: "123456", ...overrides }), generatePhoneNumber: (valid = true) => { if (valid) { const numbers = ["010", "011", "016", "017", "018", "019"]; const prefix = numbers[Math.floor(Math.random() * numbers.length)]; const suffix = Math.floor(Math.random() * 1e8).toString().padStart(8, "0"); return prefix + suffix; } else { return "01000000000"; } } }; var TestSetup = { /** * Create a test environment with mock providers */ createTestEnvironment: () => { const mockProviders = { healthy: new MockProvider("healthy", "Healthy Provider"), unhealthy: new MockProvider("unhealthy", "Unhealthy Provider"), rateLimited: new MockProvider("ratelimited", "Rate Limited Provider") }; mockProviders.unhealthy.setHealthy(false, ["Connection failed", "Authentication error"]); mockProviders.rateLimited.setHealthy(true); return { providers: mockProviders, cleanup: () => { } }; }, /** * Create test data for various scenarios */ createTestScenarios: () => ({ validMessage: { templateCode: "valid_template", phoneNumber: TestData.generatePhoneNumber(true), variables: TestData.createMockVariables() }, invalidMessage: { templateCode: "invalid_template", phoneNumber: TestData.generatePhoneNumber(false), variables: {} }, templates: [ TestData.createMockTemplate({ templateCode: "template_001", templateName: "Welcome Message" }), TestData.createMockTemplate({ templateCode: "template_002", templateName: "OTP Message" }), TestData.createMockTemplate({ templateCode: "template_003", templateName: "Notification" }) ], history: [ TestData.createMockMessage({ seqNo: 1, templateCode: "template_001" }), TestData.createMockMessage({ seqNo: 2, templateCode: "template_002" }), TestData.createMockMessage({ seqNo: 3, templateCode: "template_003" }) ] }) }; var PerformanceTest = { /** * Measure execution time of a function */ measureTime: async (fn) => { const start = performance.now(); const result = await fn(); const duration = performance.now() - start; return { result, duration }; }, /** * Run a function multiple times and get statistics */ benchmark: async (fn, iterations = 10) => { const results = []; const durations = []; for (let i = 0; i < iterations; i++) { const { result, duration } = await PerformanceTest.measureTime(fn); results.push(result); durations.push(duration); } durations.sort((a, b) => a - b); const statistics = { min: durations[0], max: durations[durations.length - 1], average: durations.reduce((sum, d) => sum + d, 0) / durations.length, median: durations[Math.floor(durations.length / 2)] }; return { results, statistics }; } }; // src/retry.ts var RetryHandler = class { static defaultOptions = { maxAttempts: 3, initialDelay: 1e3, maxDelay: 3e4, backoffMultiplier: 2, jitter: true, retryCondition: (error) => ErrorUtils.isRetryable(error) }; static async execute(operation, options = {}) { const opts = { ...this.defaultOptions, ...options }; let lastError; let delay = opts.initialDelay; for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt === opts.maxAttempts || !opts.retryCondition(lastError, attempt)) { throw lastError; } const actualDelay = opts.jitter ? delay + Math.random() * delay * 0.1 : delay; opts.onRetry?.(lastError, attempt); await new Promise((resolve) => setTimeout(resolve, actualDelay)); delay = Math.min(delay * opts.backoffMultiplier, opts.maxDelay); } } throw lastError; } static createRetryableFunction(func, options = {}) { return async (...args) => { return this.execute(() => func(...args), options); }; } }; var CircuitBreaker = class { constructor(options) { this.options = options; } state = "CLOSED"; failureCount = 0; lastFailureTime = 0; nextAttemptTime = 0; async execute(operation) { const now = Date.now(); switch (this.state) { case "OPEN": if (now < this.nextAttemptTime) { throw new KMessageError( "NETWORK_SERVICE_UNAVAILABLE" /* NETWORK_SERVICE_UNAVAILABLE */, "Circuit breaker is OPEN", { state: this.state, nextAttemptTime: this.nextAttemptTime } ); } this.state = "HALF_OPEN"; this.options.onHalfOpen?.(); break; case "HALF_OPEN": break; case "CLOSED": break; } try { const result = await Promise.race([ operation(), new Promise( (_, reject) => setTimeout(() => reject( new KMessageError( "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */, "Circuit breaker timeout", { timeout: this.options.timeout } ) ), this.options.timeout) ) ]); if (this.state === "HALF_OPEN") { this.state = "CLOSED"; this.failureCount = 0; this.options.onClose?.(); } return result; } catch (error) { this.recordFailure(); throw error; } } recordFailure() { this.failureCount++; this.lastFailureTime = Date.now(); if (this.failureCount >= this.options.failureThreshold) { this.state = "OPEN"; this.nextAttemptTime = this.lastFailureTime + this.options.resetTimeout; this.options.onOpen?.(); } } getState() { return this.state; } getFailureCount() { return this.failureCount; } reset() { this.state = "CLOSED"; this.failureCount = 0; this.lastFailureTime = 0; this.nextAttemptTime = 0; } }; var BulkOperationHandler = class { static async execute(items, operation, options = {}) { const opts = { concurrency: 5, retryOptions: { maxAttempts: 3, initialDelay: 1e3, maxDelay: 1e4, backoffMultiplier: 2, jitter: true }, failFast: false, ...options }; const startTime = Date.now(); const successful = []; const failed = []; let completed = 0; const retryableOperation = RetryHandler.createRetryableFunction( operation, opts.retryOptions ); const batches = this.createBatches(items, opts.concurrency); for (const batch of batches) { const batchPromises = batch.map(async (item) => { try { const result = await retryableOperation(item); successful.push({ item, result }); } catch (error) { failed.push({ item, error }); if (opts.failFast) { throw new KMessageError( "MESSAGE_SEND_FAILED" /* MESSAGE_SEND_FAILED */, `Bulk operation failed fast after ${failed.length} failures`, { totalItems: items.length, failedCount: failed.length } ); } } finally { completed++; opts.onProgress?.(completed, items.length, failed.length); } }); await Promise.allSettled(batchPromises); if (opts.failFast && failed.length > 0) { break; } } const duration = Date.now() - startTime; return { successful, failed, summary: { total: items.length, successful: successful.length, failed: failed.length, duration } }; } static createBatches(items, batchSize) { const batches = []; for (let i = 0; i < items.length; i += batchSize) { batches.push(items.slice(i, i + batchSize)); } return batches; } }; var RateLimiter = class { constructor(maxRequests, windowMs) { this.maxRequests = maxRequests; this.windowMs = windowMs; } requests = []; async acquire() { const now = Date.now(); this.requests = this.requests.filter((time) => now - time < this.windowMs); if (this.requests.length >= this.maxRequests) { const oldestRequest = Math.min(...this.requests); const waitTime = this.windowMs - (now - oldestRequest); if (waitTime > 0) { await new Promise((resolve) => setTimeout(resolve, waitTime)); return this.acquire(); } } this.requests.push(now); } canMakeRequest() { const now = Date.now(); this.requests = this.requests.filter((time) => now - time < this.windowMs); return this.requests.length < this.maxRequests; } getRemainingRequests() { const now = Date.now(); this.requests = this.requests.filter((time) => now - time < this.windowMs); return Math.max(0, this.maxRequests - this.requests.length); } }; var HealthMonitor = class { constructor(services, checkIntervalMs = 3e4) { this.services = services; this.checkInterval = checkIntervalMs; } healthStatus = /* @__PURE__ */ new Map(); lastCheck = /* @__PURE__ */ new Map(); checkInterval; intervalId; start() { this.intervalId = setInterval(() => { this.checkAllServices(); }, this.checkInterval); this.checkAllServices(); } stop() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = void 0; } } async checkAllServices() { const checks = Array.from(this.services.entries()).map( async ([serviceName, healthCheck]) => { try { const isHealthy = await healthCheck(); const wasHealthy = this.healthStatus.get(serviceName); this.healthStatus.set(serviceName, isHealthy); this.lastCheck.set(serviceName, Date.now()); if (wasHealthy !== void 0 && wasHealthy !== isHealthy) { console.log(`Service ${serviceName} status changed: ${wasHealthy ? "healthy" : "unhealthy"} -> ${isHealthy ? "healthy" : "unhealthy"}`); } } catch (error) { this.healthStatus.set(serviceName, false); this.lastCheck.set(serviceName, Date.now()); console.error(`Health check failed for ${serviceName}:`, error); } } ); await Promise.allSettled(checks); } getServiceHealth(serviceName) { return this.healthStatus.get(serviceName); } getAllHealth() { return Object.fromEntries(this.healthStatus); } isServiceHealthy(serviceName) { return this.healthStatus.get(serviceName) === true; } getLastCheckTime(serviceName) { return this.lastCheck.get(serviceName); } }; var GracefulDegradation = class { fallbackStrategies = /* @__PURE__ */ new Map(); registerFallback(operationName, fallbackFunction) { this.fallbackStrategies.set(operationName, fallbackFunction); } async executeWithFallback(operationName, primaryOperation, options = {}) { const timeout = options.timeout || 1e4; try { const result = await Promise.race([ RetryHandler.execute(primaryOperation, options.retryOptions), new Promise( (_, reject) => setTimeout(() => reject( new KMessageError( "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */, `Operation ${operationName} timed out`, { operationName, timeout } ) ), timeout) ) ]); return result; } catch (error) { console.warn(`Primary operation ${operationName} failed, attempting fallback:`, error); const fallback = this.fallbackStrategies.get(operationName); if (fallback) { try { return await fallback(); } catch (fallbackError) { throw new KMessageError( "UNKNOWN_ERROR" /* UNKNOWN_ERROR */, `Both primary and fallback operations failed for ${operationName}`, { operationName, primaryError: error.message, fallbackError: fallbackError.message }, { cause: error } ); } } throw error; } } }; var ErrorRecovery = { /** * Create a resilient function that combines multiple recovery patterns */ createResilientFunction(func, options = {}) { let circuitBreaker; let rateLimiter; if (options.circuitBreaker) { circuitBreaker = new CircuitBreaker(options.circuitBreaker); } if (options.rateLimiter) { rateLimiter = new RateLimiter( options.rateLimiter.maxRequests, options.rateLimiter.windowMs ); } return async (...args) => { if (rateLimiter) { await rateLimiter.acquire(); } const operation = async () => { const wrappedFunc = async () => { if (options.timeout) { return Promise.race([ func(...args), new Promise( (_, reject) => setTimeout(() => reject( new KMessageError( "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */, "Operation timed out", { timeout: options.timeout } ) ), options.timeout) ) ]); } return func(...args); }; if (circuitBreaker) { return circuitBreaker.execute(wrappedFunc); } return wrappedFunc(); }; try { return await RetryHandler.execute(operation, options.retryOptions); } catch (error) { if (options.fallback) { console.warn("Primary operation failed, using fallback:", error); return options.fallback(...args); } throw error; } }; } }; // src/index.ts var TemplateCategory = /* @__PURE__ */ ((TemplateCategory2) => { TemplateCategory2["AUTHENTICATION"] = "AUTHENTICATION"; TemplateCategory2["NOTIFICATION"] = "NOTIFICATION"; TemplateCategory2["PROMOTION"] = "PROMOTION"; TemplateCategory2["INFORMATION"] = "INFORMATION"; TemplateCategory2["RESERVATION"] = "RESERVATION"; TemplateCategory2["SHIPPING"] = "SHIPPING"; TemplateCategory2["PAYMENT"] = "PAYMENT"; return TemplateCategory2; })(TemplateCategory || {}); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BulkOperationHandler, CircuitBreaker, ErrorFactory, ErrorRecovery, ErrorUtils, GracefulDegradation, HealthMonitor, KMessageError, KMessageErrorCode, MessageError, MockProvider, PerformanceTest, ProviderError, RateLimiter, Result, RetryHandler, TemplateCategory, TemplateError, TestAssertions, TestData, TestSetup }); //# sourceMappingURL=index.cjs.map