@k-msg/core 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,1015 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BulkOperationHandler: () => BulkOperationHandler,
24
+ CircuitBreaker: () => CircuitBreaker,
25
+ ErrorFactory: () => ErrorFactory,
26
+ ErrorRecovery: () => ErrorRecovery,
27
+ ErrorUtils: () => ErrorUtils,
28
+ GracefulDegradation: () => GracefulDegradation,
29
+ HealthMonitor: () => HealthMonitor,
30
+ KMessageError: () => KMessageError,
31
+ KMessageErrorCode: () => KMessageErrorCode,
32
+ MessageError: () => MessageError,
33
+ MockProvider: () => MockProvider,
34
+ PerformanceTest: () => PerformanceTest,
35
+ ProviderError: () => ProviderError,
36
+ RateLimiter: () => RateLimiter,
37
+ Result: () => Result,
38
+ RetryHandler: () => RetryHandler,
39
+ TemplateCategory: () => TemplateCategory,
40
+ TemplateError: () => TemplateError,
41
+ TestAssertions: () => TestAssertions,
42
+ TestData: () => TestData,
43
+ TestSetup: () => TestSetup
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+
47
+ // src/errors.ts
48
+ var KMessageErrorCode = /* @__PURE__ */ ((KMessageErrorCode2) => {
49
+ KMessageErrorCode2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
50
+ KMessageErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
51
+ KMessageErrorCode2["CONFIGURATION_ERROR"] = "CONFIGURATION_ERROR";
52
+ KMessageErrorCode2["PROVIDER_NOT_FOUND"] = "PROVIDER_NOT_FOUND";
53
+ KMessageErrorCode2["PROVIDER_NOT_AVAILABLE"] = "PROVIDER_NOT_AVAILABLE";
54
+ KMessageErrorCode2["PROVIDER_AUTHENTICATION_FAILED"] = "PROVIDER_AUTHENTICATION_FAILED";
55
+ KMessageErrorCode2["PROVIDER_CONNECTION_FAILED"] = "PROVIDER_CONNECTION_FAILED";
56
+ KMessageErrorCode2["PROVIDER_RATE_LIMITED"] = "PROVIDER_RATE_LIMITED";
57
+ KMessageErrorCode2["PROVIDER_INSUFFICIENT_BALANCE"] = "PROVIDER_INSUFFICIENT_BALANCE";
58
+ KMessageErrorCode2["TEMPLATE_NOT_FOUND"] = "TEMPLATE_NOT_FOUND";
59
+ KMessageErrorCode2["TEMPLATE_VALIDATION_FAILED"] = "TEMPLATE_VALIDATION_FAILED";
60
+ KMessageErrorCode2["TEMPLATE_CREATION_FAILED"] = "TEMPLATE_CREATION_FAILED";
61
+ KMessageErrorCode2["TEMPLATE_MODIFICATION_FAILED"] = "TEMPLATE_MODIFICATION_FAILED";
62
+ KMessageErrorCode2["TEMPLATE_DELETION_FAILED"] = "TEMPLATE_DELETION_FAILED";
63
+ KMessageErrorCode2["MESSAGE_SEND_FAILED"] = "MESSAGE_SEND_FAILED";
64
+ KMessageErrorCode2["MESSAGE_INVALID_PHONE_NUMBER"] = "MESSAGE_INVALID_PHONE_NUMBER";
65
+ KMessageErrorCode2["MESSAGE_INVALID_VARIABLES"] = "MESSAGE_INVALID_VARIABLES";
66
+ KMessageErrorCode2["MESSAGE_QUOTA_EXCEEDED"] = "MESSAGE_QUOTA_EXCEEDED";
67
+ KMessageErrorCode2["MESSAGE_RESERVATION_FAILED"] = "MESSAGE_RESERVATION_FAILED";
68
+ KMessageErrorCode2["MESSAGE_CANCELLATION_FAILED"] = "MESSAGE_CANCELLATION_FAILED";
69
+ KMessageErrorCode2["NETWORK_TIMEOUT"] = "NETWORK_TIMEOUT";
70
+ KMessageErrorCode2["NETWORK_CONNECTION_FAILED"] = "NETWORK_CONNECTION_FAILED";
71
+ KMessageErrorCode2["NETWORK_SERVICE_UNAVAILABLE"] = "NETWORK_SERVICE_UNAVAILABLE";
72
+ KMessageErrorCode2["API_INVALID_REQUEST"] = "API_INVALID_REQUEST";
73
+ KMessageErrorCode2["API_UNAUTHORIZED"] = "API_UNAUTHORIZED";
74
+ KMessageErrorCode2["API_FORBIDDEN"] = "API_FORBIDDEN";
75
+ KMessageErrorCode2["API_NOT_FOUND"] = "API_NOT_FOUND";
76
+ KMessageErrorCode2["API_TOO_MANY_REQUESTS"] = "API_TOO_MANY_REQUESTS";
77
+ KMessageErrorCode2["API_INTERNAL_SERVER_ERROR"] = "API_INTERNAL_SERVER_ERROR";
78
+ return KMessageErrorCode2;
79
+ })(KMessageErrorCode || {});
80
+ var KMessageError = class _KMessageError extends Error {
81
+ code;
82
+ context;
83
+ retryable;
84
+ statusCode;
85
+ cause;
86
+ constructor(code, message, context = {}, options = {}) {
87
+ super(message);
88
+ this.name = "KMessageError";
89
+ this.code = code;
90
+ this.context = {
91
+ ...context,
92
+ timestamp: context.timestamp || /* @__PURE__ */ new Date()
93
+ };
94
+ this.retryable = options.retryable ?? this.isRetryableByDefault(code);
95
+ this.statusCode = options.statusCode;
96
+ this.cause = options.cause;
97
+ if (Error.captureStackTrace) {
98
+ Error.captureStackTrace(this, _KMessageError);
99
+ }
100
+ }
101
+ isRetryableByDefault(code) {
102
+ const retryableCodes = [
103
+ "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */,
104
+ "NETWORK_CONNECTION_FAILED" /* NETWORK_CONNECTION_FAILED */,
105
+ "NETWORK_SERVICE_UNAVAILABLE" /* NETWORK_SERVICE_UNAVAILABLE */,
106
+ "PROVIDER_CONNECTION_FAILED" /* PROVIDER_CONNECTION_FAILED */,
107
+ "PROVIDER_RATE_LIMITED" /* PROVIDER_RATE_LIMITED */,
108
+ "API_TOO_MANY_REQUESTS" /* API_TOO_MANY_REQUESTS */,
109
+ "API_INTERNAL_SERVER_ERROR" /* API_INTERNAL_SERVER_ERROR */
110
+ ];
111
+ return retryableCodes.includes(code);
112
+ }
113
+ toJSON() {
114
+ return {
115
+ name: this.name,
116
+ code: this.code,
117
+ message: this.message,
118
+ context: this.context,
119
+ retryable: this.retryable,
120
+ statusCode: this.statusCode,
121
+ stack: this.stack
122
+ };
123
+ }
124
+ };
125
+ var ProviderError = class extends KMessageError {
126
+ constructor(providerId, code, message, context = {}, options = {}) {
127
+ super(code, message, { ...context, providerId }, options);
128
+ this.name = "ProviderError";
129
+ }
130
+ };
131
+ var TemplateError = class extends KMessageError {
132
+ constructor(templateCode, code, message, context = {}, options = {}) {
133
+ super(code, message, { ...context, templateCode }, options);
134
+ this.name = "TemplateError";
135
+ }
136
+ };
137
+ var MessageError = class extends KMessageError {
138
+ constructor(phoneNumber, code, message, context = {}, options = {}) {
139
+ super(code, message, { ...context, phoneNumber }, options);
140
+ this.name = "MessageError";
141
+ }
142
+ };
143
+ var ErrorFactory = {
144
+ providerNotFound: (providerId) => new ProviderError(
145
+ providerId,
146
+ "PROVIDER_NOT_FOUND" /* PROVIDER_NOT_FOUND */,
147
+ `Provider '${providerId}' not found`,
148
+ {},
149
+ { retryable: false, statusCode: 404 }
150
+ ),
151
+ providerNotAvailable: (providerId, reason) => new ProviderError(
152
+ providerId,
153
+ "PROVIDER_NOT_AVAILABLE" /* PROVIDER_NOT_AVAILABLE */,
154
+ `Provider '${providerId}' is not available${reason ? `: ${reason}` : ""}`,
155
+ {},
156
+ { retryable: true, statusCode: 503 }
157
+ ),
158
+ authenticationFailed: (providerId, details) => new ProviderError(
159
+ providerId,
160
+ "PROVIDER_AUTHENTICATION_FAILED" /* PROVIDER_AUTHENTICATION_FAILED */,
161
+ `Authentication failed for provider '${providerId}'${details ? `: ${details}` : ""}`,
162
+ {},
163
+ { retryable: false, statusCode: 401 }
164
+ ),
165
+ templateNotFound: (templateCode) => new TemplateError(
166
+ templateCode,
167
+ "TEMPLATE_NOT_FOUND" /* TEMPLATE_NOT_FOUND */,
168
+ `Template '${templateCode}' not found`,
169
+ {},
170
+ { retryable: false, statusCode: 404 }
171
+ ),
172
+ invalidPhoneNumber: (phoneNumber) => new MessageError(
173
+ phoneNumber,
174
+ "MESSAGE_INVALID_PHONE_NUMBER" /* MESSAGE_INVALID_PHONE_NUMBER */,
175
+ `Invalid phone number format: ${phoneNumber}`,
176
+ {},
177
+ { retryable: false, statusCode: 400 }
178
+ ),
179
+ networkTimeout: (providerId, timeout) => new KMessageError(
180
+ "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */,
181
+ `Network request timed out${timeout ? ` after ${timeout}ms` : ""}`,
182
+ { providerId },
183
+ { retryable: true, statusCode: 408 }
184
+ ),
185
+ rateLimited: (providerId, retryAfter) => new ProviderError(
186
+ providerId,
187
+ "PROVIDER_RATE_LIMITED" /* PROVIDER_RATE_LIMITED */,
188
+ `Rate limit exceeded for provider '${providerId}'${retryAfter ? `. Retry after ${retryAfter}s` : ""}`,
189
+ { retryAfter },
190
+ { retryable: true, statusCode: 429 }
191
+ ),
192
+ insufficientBalance: (providerId, balance) => new ProviderError(
193
+ providerId,
194
+ "PROVIDER_INSUFFICIENT_BALANCE" /* PROVIDER_INSUFFICIENT_BALANCE */,
195
+ `Insufficient balance for provider '${providerId}'${balance ? `. Current balance: ${balance}` : ""}`,
196
+ { balance },
197
+ { retryable: false, statusCode: 402 }
198
+ ),
199
+ fromHttpStatus: (statusCode, message, context = {}) => {
200
+ let code;
201
+ let retryable = false;
202
+ switch (statusCode) {
203
+ case 400:
204
+ code = "API_INVALID_REQUEST" /* API_INVALID_REQUEST */;
205
+ break;
206
+ case 401:
207
+ code = "API_UNAUTHORIZED" /* API_UNAUTHORIZED */;
208
+ break;
209
+ case 403:
210
+ code = "API_FORBIDDEN" /* API_FORBIDDEN */;
211
+ break;
212
+ case 404:
213
+ code = "API_NOT_FOUND" /* API_NOT_FOUND */;
214
+ break;
215
+ case 408:
216
+ code = "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */;
217
+ retryable = true;
218
+ break;
219
+ case 429:
220
+ code = "API_TOO_MANY_REQUESTS" /* API_TOO_MANY_REQUESTS */;
221
+ retryable = true;
222
+ break;
223
+ case 500:
224
+ case 502:
225
+ case 503:
226
+ case 504:
227
+ code = "API_INTERNAL_SERVER_ERROR" /* API_INTERNAL_SERVER_ERROR */;
228
+ retryable = true;
229
+ break;
230
+ default:
231
+ code = "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
232
+ retryable = statusCode >= 500 && statusCode < 600;
233
+ }
234
+ return new KMessageError(code, message, context, { retryable, statusCode });
235
+ }
236
+ };
237
+ var Result = {
238
+ success: (data) => ({ success: true, data }),
239
+ failure: (error) => ({ success: false, error }),
240
+ fromPromise: async (promise) => {
241
+ try {
242
+ const data = await promise;
243
+ return Result.success(data);
244
+ } catch (error) {
245
+ if (error instanceof KMessageError) {
246
+ return Result.failure(error);
247
+ }
248
+ return Result.failure(new KMessageError(
249
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
250
+ error instanceof Error ? error.message : "Unknown error occurred",
251
+ {},
252
+ { cause: error instanceof Error ? error : void 0 }
253
+ ));
254
+ }
255
+ }
256
+ };
257
+ var ErrorUtils = {
258
+ isRetryable: (error) => {
259
+ if (error instanceof KMessageError) {
260
+ return error.retryable;
261
+ }
262
+ const retryableMessages = ["timeout", "ECONNRESET", "ECONNREFUSED", "ETIMEDOUT"];
263
+ return retryableMessages.some(
264
+ (msg) => error.message.toLowerCase().includes(msg.toLowerCase())
265
+ );
266
+ },
267
+ getStatusCode: (error) => {
268
+ if (error instanceof KMessageError && error.statusCode) {
269
+ return error.statusCode;
270
+ }
271
+ return 500;
272
+ },
273
+ formatErrorForClient: (error) => {
274
+ if (error instanceof KMessageError) {
275
+ return {
276
+ code: error.code,
277
+ message: error.message,
278
+ retryable: error.retryable,
279
+ context: error.context
280
+ };
281
+ }
282
+ return {
283
+ code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
284
+ message: error.message || "Unknown error occurred",
285
+ retryable: false,
286
+ context: {}
287
+ };
288
+ },
289
+ formatErrorForLogging: (error) => {
290
+ const baseInfo = {
291
+ name: error.name,
292
+ message: error.message,
293
+ stack: error.stack
294
+ };
295
+ if (error instanceof KMessageError) {
296
+ return {
297
+ ...baseInfo,
298
+ code: error.code,
299
+ context: error.context,
300
+ retryable: error.retryable,
301
+ statusCode: error.statusCode
302
+ };
303
+ }
304
+ return baseInfo;
305
+ }
306
+ };
307
+
308
+ // src/test-utils.ts
309
+ var import_bun_test = require("bun:test");
310
+ var MockProvider = class {
311
+ id;
312
+ name;
313
+ _healthy = true;
314
+ _issues = [];
315
+ _balance = "1000";
316
+ _templates = [];
317
+ _history = [];
318
+ constructor(id = "mock", name = "Mock Provider") {
319
+ this.id = id;
320
+ this.name = name;
321
+ }
322
+ // Health check simulation
323
+ async healthCheck() {
324
+ return {
325
+ healthy: this._healthy,
326
+ issues: [...this._issues],
327
+ data: {
328
+ balance: this._balance,
329
+ status: this._healthy ? "connected" : "disconnected",
330
+ code: this._healthy ? 200 : 500,
331
+ message: this._healthy ? "OK" : "Service unavailable"
332
+ }
333
+ };
334
+ }
335
+ // Test helpers
336
+ setHealthy(healthy, issues = []) {
337
+ this._healthy = healthy;
338
+ this._issues = issues;
339
+ }
340
+ setBalance(balance) {
341
+ this._balance = balance;
342
+ }
343
+ setTemplates(templates) {
344
+ this._templates = templates;
345
+ }
346
+ setHistory(history) {
347
+ this._history = history;
348
+ }
349
+ // Provider methods with mock implementations
350
+ async sendMessage(templateCode, phoneNumber, variables, options) {
351
+ if (!this._healthy) {
352
+ throw new MessageError(phoneNumber, "MESSAGE_SEND_FAILED" /* MESSAGE_SEND_FAILED */, "Provider is unhealthy");
353
+ }
354
+ if (phoneNumber === "01000000000") {
355
+ throw new MessageError(phoneNumber, "MESSAGE_INVALID_PHONE_NUMBER" /* MESSAGE_INVALID_PHONE_NUMBER */, "Invalid phone number");
356
+ }
357
+ return {
358
+ success: true,
359
+ messageId: `mock_${Date.now()}`,
360
+ status: "sent",
361
+ error: null
362
+ };
363
+ }
364
+ async getTemplates(page = 1, size = 15, filters) {
365
+ if (!this._healthy) {
366
+ throw new ProviderError(this.id, "PROVIDER_NOT_AVAILABLE" /* PROVIDER_NOT_AVAILABLE */, "Provider is unhealthy");
367
+ }
368
+ const start = (page - 1) * size;
369
+ const end = start + size;
370
+ const list = this._templates.slice(start, end);
371
+ return {
372
+ code: 200,
373
+ message: "Success",
374
+ totalCount: this._templates.length,
375
+ list
376
+ };
377
+ }
378
+ async createTemplate(name, content, category, buttons) {
379
+ if (!this._healthy) {
380
+ throw new TemplateError(name, "TEMPLATE_CREATION_FAILED" /* TEMPLATE_CREATION_FAILED */, "Provider is unhealthy");
381
+ }
382
+ if (name === "invalid_template") {
383
+ throw new TemplateError(name, "TEMPLATE_VALIDATION_FAILED" /* TEMPLATE_VALIDATION_FAILED */, "Template validation failed");
384
+ }
385
+ const templateCode = `mock_${name}_${Date.now()}`;
386
+ this._templates.push({
387
+ templateCode,
388
+ templateName: name,
389
+ templateContent: content,
390
+ status: "Y",
391
+ createDate: (/* @__PURE__ */ new Date()).toISOString()
392
+ });
393
+ return {
394
+ success: true,
395
+ templateCode,
396
+ status: "created",
397
+ error: null
398
+ };
399
+ }
400
+ async modifyTemplate(templateCode, name, content, buttons) {
401
+ if (!this._healthy) {
402
+ throw new TemplateError(templateCode, "TEMPLATE_MODIFICATION_FAILED" /* TEMPLATE_MODIFICATION_FAILED */, "Provider is unhealthy");
403
+ }
404
+ const template = this._templates.find((t) => t.templateCode === templateCode);
405
+ if (!template) {
406
+ throw new TemplateError(templateCode, "TEMPLATE_NOT_FOUND" /* TEMPLATE_NOT_FOUND */, "Template not found");
407
+ }
408
+ template.templateName = name;
409
+ template.templateContent = content;
410
+ return {
411
+ success: true,
412
+ templateCode,
413
+ status: "modified",
414
+ error: null
415
+ };
416
+ }
417
+ async deleteTemplate(templateCode) {
418
+ if (!this._healthy) {
419
+ throw new TemplateError(templateCode, "TEMPLATE_DELETION_FAILED" /* TEMPLATE_DELETION_FAILED */, "Provider is unhealthy");
420
+ }
421
+ const index = this._templates.findIndex((t) => t.templateCode === templateCode);
422
+ if (index === -1) {
423
+ return {
424
+ code: 404,
425
+ message: "Template not found"
426
+ };
427
+ }
428
+ this._templates.splice(index, 1);
429
+ return {
430
+ code: 200,
431
+ message: "Template deleted successfully"
432
+ };
433
+ }
434
+ async getHistory(page = 1, size = 15, filters) {
435
+ if (!this._healthy) {
436
+ throw new ProviderError(this.id, "PROVIDER_NOT_AVAILABLE" /* PROVIDER_NOT_AVAILABLE */, "Provider is unhealthy");
437
+ }
438
+ const start = (page - 1) * size;
439
+ const end = start + size;
440
+ const list = this._history.slice(start, end);
441
+ return {
442
+ code: 200,
443
+ message: "Success",
444
+ totalCount: this._history.length,
445
+ list
446
+ };
447
+ }
448
+ async cancelReservation(messageId) {
449
+ if (!this._healthy) {
450
+ throw new KMessageError("MESSAGE_CANCELLATION_FAILED" /* MESSAGE_CANCELLATION_FAILED */, "Provider is unhealthy");
451
+ }
452
+ return {
453
+ code: 200,
454
+ message: "Reservation cancelled successfully"
455
+ };
456
+ }
457
+ };
458
+ var TestAssertions = {
459
+ /**
460
+ * Assert that an error is a KMessageError with specific code
461
+ */
462
+ assertKMessageError: (error, expectedCode, expectedMessage) => {
463
+ (0, import_bun_test.expect)(error).toBeInstanceOf(KMessageError);
464
+ const kError = error;
465
+ (0, import_bun_test.expect)(kError.code).toBe(expectedCode);
466
+ if (expectedMessage) {
467
+ (0, import_bun_test.expect)(kError.message).toContain(expectedMessage);
468
+ }
469
+ },
470
+ /**
471
+ * Assert that an error is retryable
472
+ */
473
+ assertRetryable: (error, expected = true) => {
474
+ (0, import_bun_test.expect)(error.retryable).toBe(expected);
475
+ },
476
+ /**
477
+ * Assert that a health status is healthy
478
+ */
479
+ assertHealthy: (health, expected = true) => {
480
+ (0, import_bun_test.expect)(health.healthy).toBe(expected);
481
+ if (!expected) {
482
+ (0, import_bun_test.expect)(health.issues.length).toBeGreaterThan(0);
483
+ }
484
+ },
485
+ /**
486
+ * Assert that a provider result has expected structure
487
+ */
488
+ assertProviderResult: (result, expectSuccess = true) => {
489
+ (0, import_bun_test.expect)(result).toHaveProperty("success");
490
+ (0, import_bun_test.expect)(result.success).toBe(expectSuccess);
491
+ if (expectSuccess) {
492
+ (0, import_bun_test.expect)(result.error).toBeNull();
493
+ } else {
494
+ (0, import_bun_test.expect)(result.error).toBeDefined();
495
+ }
496
+ },
497
+ /**
498
+ * Assert that API response has expected structure
499
+ */
500
+ assertApiResponse: (response, expectedCode = 200) => {
501
+ (0, import_bun_test.expect)(response).toHaveProperty("code");
502
+ (0, import_bun_test.expect)(response).toHaveProperty("message");
503
+ (0, import_bun_test.expect)(response.code).toBe(expectedCode);
504
+ }
505
+ };
506
+ var TestData = {
507
+ createMockTemplate: (overrides = {}) => ({
508
+ templateCode: "mock_template_001",
509
+ templateName: "Mock Template",
510
+ templateContent: "[#{\uC11C\uBE44\uC2A4\uBA85}] \uC548\uB155\uD558\uC138\uC694, #{\uACE0\uAC1D\uBA85}\uB2D8!",
511
+ status: "Y",
512
+ createDate: "2024-01-01 12:00:00",
513
+ ...overrides
514
+ }),
515
+ createMockMessage: (overrides = {}) => ({
516
+ seqNo: 12345,
517
+ phone: "01012345678",
518
+ templateCode: "mock_template_001",
519
+ statusCode: "OK",
520
+ statusCodeName: "\uC131\uACF5",
521
+ requestDate: "2024-01-01 12:00:00",
522
+ sendDate: "2024-01-01 12:01:00",
523
+ receiveDate: "2024-01-01 12:01:30",
524
+ sendMessage: "[MyApp] \uC548\uB155\uD558\uC138\uC694, \uD64D\uAE38\uB3D9\uB2D8!",
525
+ ...overrides
526
+ }),
527
+ createMockVariables: (overrides = {}) => ({
528
+ \uC11C\uBE44\uC2A4\uBA85: "MyApp",
529
+ \uACE0\uAC1D\uBA85: "\uD64D\uAE38\uB3D9",
530
+ \uC778\uC99D\uCF54\uB4DC: "123456",
531
+ ...overrides
532
+ }),
533
+ generatePhoneNumber: (valid = true) => {
534
+ if (valid) {
535
+ const numbers = ["010", "011", "016", "017", "018", "019"];
536
+ const prefix = numbers[Math.floor(Math.random() * numbers.length)];
537
+ const suffix = Math.floor(Math.random() * 1e8).toString().padStart(8, "0");
538
+ return prefix + suffix;
539
+ } else {
540
+ return "01000000000";
541
+ }
542
+ }
543
+ };
544
+ var TestSetup = {
545
+ /**
546
+ * Create a test environment with mock providers
547
+ */
548
+ createTestEnvironment: () => {
549
+ const mockProviders = {
550
+ healthy: new MockProvider("healthy", "Healthy Provider"),
551
+ unhealthy: new MockProvider("unhealthy", "Unhealthy Provider"),
552
+ rateLimited: new MockProvider("ratelimited", "Rate Limited Provider")
553
+ };
554
+ mockProviders.unhealthy.setHealthy(false, ["Connection failed", "Authentication error"]);
555
+ mockProviders.rateLimited.setHealthy(true);
556
+ return {
557
+ providers: mockProviders,
558
+ cleanup: () => {
559
+ }
560
+ };
561
+ },
562
+ /**
563
+ * Create test data for various scenarios
564
+ */
565
+ createTestScenarios: () => ({
566
+ validMessage: {
567
+ templateCode: "valid_template",
568
+ phoneNumber: TestData.generatePhoneNumber(true),
569
+ variables: TestData.createMockVariables()
570
+ },
571
+ invalidMessage: {
572
+ templateCode: "invalid_template",
573
+ phoneNumber: TestData.generatePhoneNumber(false),
574
+ variables: {}
575
+ },
576
+ templates: [
577
+ TestData.createMockTemplate({ templateCode: "template_001", templateName: "Welcome Message" }),
578
+ TestData.createMockTemplate({ templateCode: "template_002", templateName: "OTP Message" }),
579
+ TestData.createMockTemplate({ templateCode: "template_003", templateName: "Notification" })
580
+ ],
581
+ history: [
582
+ TestData.createMockMessage({ seqNo: 1, templateCode: "template_001" }),
583
+ TestData.createMockMessage({ seqNo: 2, templateCode: "template_002" }),
584
+ TestData.createMockMessage({ seqNo: 3, templateCode: "template_003" })
585
+ ]
586
+ })
587
+ };
588
+ var PerformanceTest = {
589
+ /**
590
+ * Measure execution time of a function
591
+ */
592
+ measureTime: async (fn) => {
593
+ const start = performance.now();
594
+ const result = await fn();
595
+ const duration = performance.now() - start;
596
+ return { result, duration };
597
+ },
598
+ /**
599
+ * Run a function multiple times and get statistics
600
+ */
601
+ benchmark: async (fn, iterations = 10) => {
602
+ const results = [];
603
+ const durations = [];
604
+ for (let i = 0; i < iterations; i++) {
605
+ const { result, duration } = await PerformanceTest.measureTime(fn);
606
+ results.push(result);
607
+ durations.push(duration);
608
+ }
609
+ durations.sort((a, b) => a - b);
610
+ const statistics = {
611
+ min: durations[0],
612
+ max: durations[durations.length - 1],
613
+ average: durations.reduce((sum, d) => sum + d, 0) / durations.length,
614
+ median: durations[Math.floor(durations.length / 2)]
615
+ };
616
+ return { results, statistics };
617
+ }
618
+ };
619
+
620
+ // src/retry.ts
621
+ var RetryHandler = class {
622
+ static defaultOptions = {
623
+ maxAttempts: 3,
624
+ initialDelay: 1e3,
625
+ maxDelay: 3e4,
626
+ backoffMultiplier: 2,
627
+ jitter: true,
628
+ retryCondition: (error) => ErrorUtils.isRetryable(error)
629
+ };
630
+ static async execute(operation, options = {}) {
631
+ const opts = { ...this.defaultOptions, ...options };
632
+ let lastError;
633
+ let delay = opts.initialDelay;
634
+ for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
635
+ try {
636
+ return await operation();
637
+ } catch (error) {
638
+ lastError = error;
639
+ if (attempt === opts.maxAttempts || !opts.retryCondition(lastError, attempt)) {
640
+ throw lastError;
641
+ }
642
+ const actualDelay = opts.jitter ? delay + Math.random() * delay * 0.1 : delay;
643
+ opts.onRetry?.(lastError, attempt);
644
+ await new Promise((resolve) => setTimeout(resolve, actualDelay));
645
+ delay = Math.min(delay * opts.backoffMultiplier, opts.maxDelay);
646
+ }
647
+ }
648
+ throw lastError;
649
+ }
650
+ static createRetryableFunction(func, options = {}) {
651
+ return async (...args) => {
652
+ return this.execute(() => func(...args), options);
653
+ };
654
+ }
655
+ };
656
+ var CircuitBreaker = class {
657
+ constructor(options) {
658
+ this.options = options;
659
+ }
660
+ state = "CLOSED";
661
+ failureCount = 0;
662
+ lastFailureTime = 0;
663
+ nextAttemptTime = 0;
664
+ async execute(operation) {
665
+ const now = Date.now();
666
+ switch (this.state) {
667
+ case "OPEN":
668
+ if (now < this.nextAttemptTime) {
669
+ throw new KMessageError(
670
+ "NETWORK_SERVICE_UNAVAILABLE" /* NETWORK_SERVICE_UNAVAILABLE */,
671
+ "Circuit breaker is OPEN",
672
+ { state: this.state, nextAttemptTime: this.nextAttemptTime }
673
+ );
674
+ }
675
+ this.state = "HALF_OPEN";
676
+ this.options.onHalfOpen?.();
677
+ break;
678
+ case "HALF_OPEN":
679
+ break;
680
+ case "CLOSED":
681
+ break;
682
+ }
683
+ try {
684
+ const result = await Promise.race([
685
+ operation(),
686
+ new Promise(
687
+ (_, reject) => setTimeout(() => reject(
688
+ new KMessageError(
689
+ "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */,
690
+ "Circuit breaker timeout",
691
+ { timeout: this.options.timeout }
692
+ )
693
+ ), this.options.timeout)
694
+ )
695
+ ]);
696
+ if (this.state === "HALF_OPEN") {
697
+ this.state = "CLOSED";
698
+ this.failureCount = 0;
699
+ this.options.onClose?.();
700
+ }
701
+ return result;
702
+ } catch (error) {
703
+ this.recordFailure();
704
+ throw error;
705
+ }
706
+ }
707
+ recordFailure() {
708
+ this.failureCount++;
709
+ this.lastFailureTime = Date.now();
710
+ if (this.failureCount >= this.options.failureThreshold) {
711
+ this.state = "OPEN";
712
+ this.nextAttemptTime = this.lastFailureTime + this.options.resetTimeout;
713
+ this.options.onOpen?.();
714
+ }
715
+ }
716
+ getState() {
717
+ return this.state;
718
+ }
719
+ getFailureCount() {
720
+ return this.failureCount;
721
+ }
722
+ reset() {
723
+ this.state = "CLOSED";
724
+ this.failureCount = 0;
725
+ this.lastFailureTime = 0;
726
+ this.nextAttemptTime = 0;
727
+ }
728
+ };
729
+ var BulkOperationHandler = class {
730
+ static async execute(items, operation, options = {}) {
731
+ const opts = {
732
+ concurrency: 5,
733
+ retryOptions: {
734
+ maxAttempts: 3,
735
+ initialDelay: 1e3,
736
+ maxDelay: 1e4,
737
+ backoffMultiplier: 2,
738
+ jitter: true
739
+ },
740
+ failFast: false,
741
+ ...options
742
+ };
743
+ const startTime = Date.now();
744
+ const successful = [];
745
+ const failed = [];
746
+ let completed = 0;
747
+ const retryableOperation = RetryHandler.createRetryableFunction(
748
+ operation,
749
+ opts.retryOptions
750
+ );
751
+ const batches = this.createBatches(items, opts.concurrency);
752
+ for (const batch of batches) {
753
+ const batchPromises = batch.map(async (item) => {
754
+ try {
755
+ const result = await retryableOperation(item);
756
+ successful.push({ item, result });
757
+ } catch (error) {
758
+ failed.push({ item, error });
759
+ if (opts.failFast) {
760
+ throw new KMessageError(
761
+ "MESSAGE_SEND_FAILED" /* MESSAGE_SEND_FAILED */,
762
+ `Bulk operation failed fast after ${failed.length} failures`,
763
+ { totalItems: items.length, failedCount: failed.length }
764
+ );
765
+ }
766
+ } finally {
767
+ completed++;
768
+ opts.onProgress?.(completed, items.length, failed.length);
769
+ }
770
+ });
771
+ await Promise.allSettled(batchPromises);
772
+ if (opts.failFast && failed.length > 0) {
773
+ break;
774
+ }
775
+ }
776
+ const duration = Date.now() - startTime;
777
+ return {
778
+ successful,
779
+ failed,
780
+ summary: {
781
+ total: items.length,
782
+ successful: successful.length,
783
+ failed: failed.length,
784
+ duration
785
+ }
786
+ };
787
+ }
788
+ static createBatches(items, batchSize) {
789
+ const batches = [];
790
+ for (let i = 0; i < items.length; i += batchSize) {
791
+ batches.push(items.slice(i, i + batchSize));
792
+ }
793
+ return batches;
794
+ }
795
+ };
796
+ var RateLimiter = class {
797
+ constructor(maxRequests, windowMs) {
798
+ this.maxRequests = maxRequests;
799
+ this.windowMs = windowMs;
800
+ }
801
+ requests = [];
802
+ async acquire() {
803
+ const now = Date.now();
804
+ this.requests = this.requests.filter((time) => now - time < this.windowMs);
805
+ if (this.requests.length >= this.maxRequests) {
806
+ const oldestRequest = Math.min(...this.requests);
807
+ const waitTime = this.windowMs - (now - oldestRequest);
808
+ if (waitTime > 0) {
809
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
810
+ return this.acquire();
811
+ }
812
+ }
813
+ this.requests.push(now);
814
+ }
815
+ canMakeRequest() {
816
+ const now = Date.now();
817
+ this.requests = this.requests.filter((time) => now - time < this.windowMs);
818
+ return this.requests.length < this.maxRequests;
819
+ }
820
+ getRemainingRequests() {
821
+ const now = Date.now();
822
+ this.requests = this.requests.filter((time) => now - time < this.windowMs);
823
+ return Math.max(0, this.maxRequests - this.requests.length);
824
+ }
825
+ };
826
+ var HealthMonitor = class {
827
+ constructor(services, checkIntervalMs = 3e4) {
828
+ this.services = services;
829
+ this.checkInterval = checkIntervalMs;
830
+ }
831
+ healthStatus = /* @__PURE__ */ new Map();
832
+ lastCheck = /* @__PURE__ */ new Map();
833
+ checkInterval;
834
+ intervalId;
835
+ start() {
836
+ this.intervalId = setInterval(() => {
837
+ this.checkAllServices();
838
+ }, this.checkInterval);
839
+ this.checkAllServices();
840
+ }
841
+ stop() {
842
+ if (this.intervalId) {
843
+ clearInterval(this.intervalId);
844
+ this.intervalId = void 0;
845
+ }
846
+ }
847
+ async checkAllServices() {
848
+ const checks = Array.from(this.services.entries()).map(
849
+ async ([serviceName, healthCheck]) => {
850
+ try {
851
+ const isHealthy = await healthCheck();
852
+ const wasHealthy = this.healthStatus.get(serviceName);
853
+ this.healthStatus.set(serviceName, isHealthy);
854
+ this.lastCheck.set(serviceName, Date.now());
855
+ if (wasHealthy !== void 0 && wasHealthy !== isHealthy) {
856
+ console.log(`Service ${serviceName} status changed: ${wasHealthy ? "healthy" : "unhealthy"} -> ${isHealthy ? "healthy" : "unhealthy"}`);
857
+ }
858
+ } catch (error) {
859
+ this.healthStatus.set(serviceName, false);
860
+ this.lastCheck.set(serviceName, Date.now());
861
+ console.error(`Health check failed for ${serviceName}:`, error);
862
+ }
863
+ }
864
+ );
865
+ await Promise.allSettled(checks);
866
+ }
867
+ getServiceHealth(serviceName) {
868
+ return this.healthStatus.get(serviceName);
869
+ }
870
+ getAllHealth() {
871
+ return Object.fromEntries(this.healthStatus);
872
+ }
873
+ isServiceHealthy(serviceName) {
874
+ return this.healthStatus.get(serviceName) === true;
875
+ }
876
+ getLastCheckTime(serviceName) {
877
+ return this.lastCheck.get(serviceName);
878
+ }
879
+ };
880
+ var GracefulDegradation = class {
881
+ fallbackStrategies = /* @__PURE__ */ new Map();
882
+ registerFallback(operationName, fallbackFunction) {
883
+ this.fallbackStrategies.set(operationName, fallbackFunction);
884
+ }
885
+ async executeWithFallback(operationName, primaryOperation, options = {}) {
886
+ const timeout = options.timeout || 1e4;
887
+ try {
888
+ const result = await Promise.race([
889
+ RetryHandler.execute(primaryOperation, options.retryOptions),
890
+ new Promise(
891
+ (_, reject) => setTimeout(() => reject(
892
+ new KMessageError(
893
+ "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */,
894
+ `Operation ${operationName} timed out`,
895
+ { operationName, timeout }
896
+ )
897
+ ), timeout)
898
+ )
899
+ ]);
900
+ return result;
901
+ } catch (error) {
902
+ console.warn(`Primary operation ${operationName} failed, attempting fallback:`, error);
903
+ const fallback = this.fallbackStrategies.get(operationName);
904
+ if (fallback) {
905
+ try {
906
+ return await fallback();
907
+ } catch (fallbackError) {
908
+ throw new KMessageError(
909
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
910
+ `Both primary and fallback operations failed for ${operationName}`,
911
+ {
912
+ operationName,
913
+ primaryError: error.message,
914
+ fallbackError: fallbackError.message
915
+ },
916
+ { cause: error }
917
+ );
918
+ }
919
+ }
920
+ throw error;
921
+ }
922
+ }
923
+ };
924
+ var ErrorRecovery = {
925
+ /**
926
+ * Create a resilient function that combines multiple recovery patterns
927
+ */
928
+ createResilientFunction(func, options = {}) {
929
+ let circuitBreaker;
930
+ let rateLimiter;
931
+ if (options.circuitBreaker) {
932
+ circuitBreaker = new CircuitBreaker(options.circuitBreaker);
933
+ }
934
+ if (options.rateLimiter) {
935
+ rateLimiter = new RateLimiter(
936
+ options.rateLimiter.maxRequests,
937
+ options.rateLimiter.windowMs
938
+ );
939
+ }
940
+ return async (...args) => {
941
+ if (rateLimiter) {
942
+ await rateLimiter.acquire();
943
+ }
944
+ const operation = async () => {
945
+ const wrappedFunc = async () => {
946
+ if (options.timeout) {
947
+ return Promise.race([
948
+ func(...args),
949
+ new Promise(
950
+ (_, reject) => setTimeout(() => reject(
951
+ new KMessageError(
952
+ "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */,
953
+ "Operation timed out",
954
+ { timeout: options.timeout }
955
+ )
956
+ ), options.timeout)
957
+ )
958
+ ]);
959
+ }
960
+ return func(...args);
961
+ };
962
+ if (circuitBreaker) {
963
+ return circuitBreaker.execute(wrappedFunc);
964
+ }
965
+ return wrappedFunc();
966
+ };
967
+ try {
968
+ return await RetryHandler.execute(operation, options.retryOptions);
969
+ } catch (error) {
970
+ if (options.fallback) {
971
+ console.warn("Primary operation failed, using fallback:", error);
972
+ return options.fallback(...args);
973
+ }
974
+ throw error;
975
+ }
976
+ };
977
+ }
978
+ };
979
+
980
+ // src/index.ts
981
+ var TemplateCategory = /* @__PURE__ */ ((TemplateCategory2) => {
982
+ TemplateCategory2["AUTHENTICATION"] = "AUTHENTICATION";
983
+ TemplateCategory2["NOTIFICATION"] = "NOTIFICATION";
984
+ TemplateCategory2["PROMOTION"] = "PROMOTION";
985
+ TemplateCategory2["INFORMATION"] = "INFORMATION";
986
+ TemplateCategory2["RESERVATION"] = "RESERVATION";
987
+ TemplateCategory2["SHIPPING"] = "SHIPPING";
988
+ TemplateCategory2["PAYMENT"] = "PAYMENT";
989
+ return TemplateCategory2;
990
+ })(TemplateCategory || {});
991
+ // Annotate the CommonJS export names for ESM import in node:
992
+ 0 && (module.exports = {
993
+ BulkOperationHandler,
994
+ CircuitBreaker,
995
+ ErrorFactory,
996
+ ErrorRecovery,
997
+ ErrorUtils,
998
+ GracefulDegradation,
999
+ HealthMonitor,
1000
+ KMessageError,
1001
+ KMessageErrorCode,
1002
+ MessageError,
1003
+ MockProvider,
1004
+ PerformanceTest,
1005
+ ProviderError,
1006
+ RateLimiter,
1007
+ Result,
1008
+ RetryHandler,
1009
+ TemplateCategory,
1010
+ TemplateError,
1011
+ TestAssertions,
1012
+ TestData,
1013
+ TestSetup
1014
+ });
1015
+ //# sourceMappingURL=index.cjs.map