@k-msg/core 0.1.1 → 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 +244 -17
- package/dist/config.d.ts +206 -0
- package/dist/errors.d.ts +27 -0
- package/dist/health.d.ts +39 -0
- package/dist/index.d.ts +12 -565
- package/dist/index.js +23 -964
- package/dist/index.js.map +89 -1
- package/dist/index.mjs +27 -0
- package/dist/index.mjs.map +89 -0
- package/dist/logger.d.ts +62 -0
- package/dist/platform.d.ts +63 -0
- package/dist/provider-registry.d.ts +135 -0
- package/dist/provider.d.ts +36 -0
- package/dist/resilience/bulk-operation.d.ts +29 -0
- package/dist/resilience/circuit-breaker.d.ts +24 -0
- package/dist/resilience/error-recovery.d.ts +20 -0
- package/dist/resilience/graceful-degradation.d.ts +12 -0
- package/dist/resilience/health-monitor.d.ts +20 -0
- package/dist/resilience/index.d.ts +7 -0
- package/dist/resilience/rate-limiter.d.ts +12 -0
- package/dist/resilience/retry-handler.d.ts +20 -0
- package/dist/result.d.ts +61 -0
- package/dist/test-utils.d.ts +12 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/message.d.ts +34 -0
- package/dist/types/platform.d.ts +88 -0
- package/dist/types/provider.d.ts +76 -0
- package/dist/types/standard.d.ts +62 -0
- package/dist/universal-provider.d.ts +55 -0
- package/package.json +20 -9
- package/dist/index.cjs +0 -1015
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -565
package/dist/index.cjs
DELETED
|
@@ -1,1015 +0,0 @@
|
|
|
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
|