@nmakarov/cli-toolkit 0.2.0 → 0.3.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/README.md +112 -1
- package/dist/{filestore.cjs → filedatabase.cjs} +191 -37
- package/dist/filedatabase.cjs.map +1 -0
- package/dist/{filestore.js → filedatabase.js} +187 -33
- package/dist/filedatabase.js.map +1 -0
- package/dist/http-client.cjs +356 -0
- package/dist/http-client.cjs.map +1 -0
- package/dist/http-client.js +318 -0
- package/dist/http-client.js.map +1 -0
- package/dist/index.cjs +186 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +186 -32
- package/dist/index.js.map +1 -1
- package/dist/mock-server.cjs +1483 -0
- package/dist/mock-server.cjs.map +1 -0
- package/dist/mock-server.js +1445 -0
- package/dist/mock-server.js.map +1 -0
- package/package.json +30 -11
- package/dist/filestore.cjs.map +0 -1
- package/dist/filestore.js.map +0 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// src/http-client/index.ts
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
4
|
+
// src/http-client/errors.ts
|
|
5
|
+
function classifyError(error) {
|
|
6
|
+
if (error.response) {
|
|
7
|
+
const { status } = error.response;
|
|
8
|
+
switch (status) {
|
|
9
|
+
case 400:
|
|
10
|
+
return { type: "badRequest", retryable: false, isAuth: false, status: "clientError" };
|
|
11
|
+
case 401:
|
|
12
|
+
return { type: "unauthorized", retryable: false, isAuth: true, status: "authRequired" };
|
|
13
|
+
case 403:
|
|
14
|
+
return { type: "forbidden", retryable: false, isAuth: true, status: "authFailed" };
|
|
15
|
+
case 404:
|
|
16
|
+
return { type: "notFound", retryable: false, isAuth: false, status: "clientError" };
|
|
17
|
+
case 405:
|
|
18
|
+
return { type: "methodNotAllowed", retryable: false, isAuth: false, status: "clientError" };
|
|
19
|
+
case 409:
|
|
20
|
+
return { type: "conflict", retryable: false, isAuth: false, status: "clientError" };
|
|
21
|
+
case 422:
|
|
22
|
+
return { type: "unprocessableEntity", retryable: false, isAuth: false, status: "clientError" };
|
|
23
|
+
case 429:
|
|
24
|
+
return { type: "tooManyRequests", retryable: true, isAuth: false, status: "clientError" };
|
|
25
|
+
case 500:
|
|
26
|
+
return { type: "internalServerError", retryable: true, isAuth: false, status: "serverError" };
|
|
27
|
+
case 502:
|
|
28
|
+
return { type: "badGateway", retryable: true, isAuth: false, status: "serverError" };
|
|
29
|
+
case 503:
|
|
30
|
+
return { type: "serviceUnavailable", retryable: true, isAuth: false, status: "serverError" };
|
|
31
|
+
case 504:
|
|
32
|
+
return { type: "gatewayTimeout", retryable: true, isAuth: false, status: "serverError" };
|
|
33
|
+
default:
|
|
34
|
+
if (status >= 400 && status < 500) {
|
|
35
|
+
return { type: "clientError", retryable: false, isAuth: false, status: "clientError" };
|
|
36
|
+
} else if (status >= 500) {
|
|
37
|
+
return { type: "serverError", retryable: true, isAuth: false, status: "serverError" };
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (error.code) {
|
|
43
|
+
switch (error.code) {
|
|
44
|
+
case "ECONNREFUSED":
|
|
45
|
+
case "ECONNRESET":
|
|
46
|
+
case "EPIPE":
|
|
47
|
+
case "ENOTFOUND":
|
|
48
|
+
case "EHOSTUNREACH":
|
|
49
|
+
case "ENETUNREACH":
|
|
50
|
+
return { type: "connectionFailed", retryable: true, isAuth: false, status: "networkError" };
|
|
51
|
+
case "ETIMEDOUT":
|
|
52
|
+
case "ECONNABORTED":
|
|
53
|
+
case "ESOCKETTIMEDOUT":
|
|
54
|
+
return { type: "timeout", retryable: true, isAuth: false, status: "timeout" };
|
|
55
|
+
case "EAUTH":
|
|
56
|
+
case "EACCES":
|
|
57
|
+
return { type: "unauthorized", retryable: false, isAuth: true, status: "authRequired" };
|
|
58
|
+
default:
|
|
59
|
+
return { type: "networkError", retryable: true, isAuth: false, status: "networkError" };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (error.message && (error.message.includes("timeout") || error.message.includes("TIMEOUT") || error.message.includes("aborted"))) {
|
|
63
|
+
return { type: "timeout", retryable: true, isAuth: false, status: "timeout" };
|
|
64
|
+
}
|
|
65
|
+
if (error.name === "AbortError" || error.message?.includes("cancelled")) {
|
|
66
|
+
return { type: "requestCancelled", retryable: false, isAuth: false, status: "unknown" };
|
|
67
|
+
}
|
|
68
|
+
return { type: "unknown", retryable: false, isAuth: false, status: "unknown" };
|
|
69
|
+
}
|
|
70
|
+
function getErrorDescription(errorType) {
|
|
71
|
+
const descriptions = {
|
|
72
|
+
connectionFailed: "Failed to establish a connection to the server",
|
|
73
|
+
timeout: "Request timed out before completing",
|
|
74
|
+
networkError: "Network connection issue occurred",
|
|
75
|
+
badRequest: "Request was malformed or invalid",
|
|
76
|
+
unauthorized: "Authentication credentials are required",
|
|
77
|
+
forbidden: "Access to the requested resource is forbidden",
|
|
78
|
+
notFound: "The requested resource was not found",
|
|
79
|
+
methodNotAllowed: "HTTP method not allowed for this resource",
|
|
80
|
+
conflict: "Request conflicts with current server state",
|
|
81
|
+
unprocessableEntity: "Request data could not be processed",
|
|
82
|
+
tooManyRequests: "Too many requests sent in a short time",
|
|
83
|
+
internalServerError: "Server encountered an internal error",
|
|
84
|
+
badGateway: "Invalid response from upstream server",
|
|
85
|
+
serviceUnavailable: "Server is temporarily unavailable",
|
|
86
|
+
gatewayTimeout: "Upstream server timed out",
|
|
87
|
+
unknown: "An unknown error occurred",
|
|
88
|
+
requestCancelled: "Request was cancelled before completion"
|
|
89
|
+
};
|
|
90
|
+
return descriptions[errorType] || "An unknown error occurred";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/http-client/retry.ts
|
|
94
|
+
function calculateRetryDelay(attempt, baseDelay, maxDelay, jitterFactor = 0.1) {
|
|
95
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
|
|
96
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
97
|
+
const jitter = cappedDelay * jitterFactor * Math.random();
|
|
98
|
+
return Math.floor(cappedDelay + jitter);
|
|
99
|
+
}
|
|
100
|
+
function sleep(ms) {
|
|
101
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
102
|
+
}
|
|
103
|
+
function shouldRetryError(classification) {
|
|
104
|
+
if (classification.isAuth) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return classification.retryable;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/http-client/index.ts
|
|
111
|
+
var HttpClientError = class extends Error {
|
|
112
|
+
constructor(message, cause) {
|
|
113
|
+
super(message);
|
|
114
|
+
this.cause = cause;
|
|
115
|
+
this.name = "HttpClientError";
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var HttpClient = class {
|
|
119
|
+
axiosInstance;
|
|
120
|
+
config;
|
|
121
|
+
logger;
|
|
122
|
+
constructor(config = {}) {
|
|
123
|
+
this.config = {
|
|
124
|
+
timeout: 3e4,
|
|
125
|
+
// 30 seconds
|
|
126
|
+
retryCount: 3,
|
|
127
|
+
// 3 retry attempts
|
|
128
|
+
retryDelay: 1e3,
|
|
129
|
+
// 1 second base delay
|
|
130
|
+
maxRetryDelay: 3e4,
|
|
131
|
+
// 30 second max delay
|
|
132
|
+
retryJitter: 0.1,
|
|
133
|
+
// 10% jitter
|
|
134
|
+
userAgent: "HttpClient/v1.0",
|
|
135
|
+
validateSSL: true,
|
|
136
|
+
maxRedirects: 5,
|
|
137
|
+
logger: console,
|
|
138
|
+
...config
|
|
139
|
+
};
|
|
140
|
+
this.logger = this.config.logger;
|
|
141
|
+
this.axiosInstance = axios.create({
|
|
142
|
+
timeout: this.config.timeout,
|
|
143
|
+
validateStatus: () => true,
|
|
144
|
+
// Never throw on HTTP status codes
|
|
145
|
+
maxRedirects: this.config.maxRedirects,
|
|
146
|
+
headers: {
|
|
147
|
+
"User-Agent": this.config.userAgent
|
|
148
|
+
},
|
|
149
|
+
// SSL validation
|
|
150
|
+
httpsAgent: this.config.validateSSL ? void 0 : {
|
|
151
|
+
rejectUnauthorized: false
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
this.axiosInstance.interceptors.response.use(
|
|
155
|
+
(response) => response,
|
|
156
|
+
(error) => {
|
|
157
|
+
return Promise.reject(error);
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Make an HTTP request with automatic retry logic
|
|
163
|
+
* Never throws - always returns HttpClientResponse
|
|
164
|
+
*/
|
|
165
|
+
async request(method, url, options = {}) {
|
|
166
|
+
const startTime = Date.now();
|
|
167
|
+
const requestConfig = {
|
|
168
|
+
method,
|
|
169
|
+
url,
|
|
170
|
+
timeout: options.timeout || this.config.timeout,
|
|
171
|
+
headers: {
|
|
172
|
+
"User-Agent": options.userAgent || this.config.userAgent,
|
|
173
|
+
...options.headers
|
|
174
|
+
},
|
|
175
|
+
params: options.params,
|
|
176
|
+
data: options.data
|
|
177
|
+
};
|
|
178
|
+
const retryCount = options.retryCount ?? this.config.retryCount;
|
|
179
|
+
const retryDelay = options.retryDelay ?? this.config.retryDelay;
|
|
180
|
+
let retryContext = null;
|
|
181
|
+
let lastError = null;
|
|
182
|
+
for (let attempt = 1; attempt <= retryCount + 1; attempt++) {
|
|
183
|
+
try {
|
|
184
|
+
if (options.debug) {
|
|
185
|
+
this.logger.debug?.(`[HttpClient] ${method} ${url} (attempt ${attempt}/${retryCount + 1})`);
|
|
186
|
+
}
|
|
187
|
+
const response = await this.axiosInstance.request(requestConfig);
|
|
188
|
+
const duration = Date.now() - startTime;
|
|
189
|
+
const customStatus = this.mapHttpStatusToCustomStatus(response.status);
|
|
190
|
+
if (options.debug) {
|
|
191
|
+
this.logger.debug?.(`[HttpClient] ${method} ${url} \u2192 ${response.status} ${customStatus} (${duration}ms)`);
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
status: customStatus,
|
|
195
|
+
code: response.status,
|
|
196
|
+
headers: response.headers,
|
|
197
|
+
data: response.data,
|
|
198
|
+
duration,
|
|
199
|
+
retryCount: attempt - 1,
|
|
200
|
+
finalUrl: response.request?.res?.responseUrl || url
|
|
201
|
+
};
|
|
202
|
+
} catch (error) {
|
|
203
|
+
lastError = error;
|
|
204
|
+
const duration = Date.now() - startTime;
|
|
205
|
+
const classification = classifyError(error);
|
|
206
|
+
const errorDescription = getErrorDescription(classification.type);
|
|
207
|
+
if (classification.retryable && attempt <= retryCount) {
|
|
208
|
+
this.logger.warn?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}. Retrying in ${retryDelay}ms...`);
|
|
209
|
+
} else if (!classification.retryable || attempt > retryCount) {
|
|
210
|
+
this.logger.error?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}`);
|
|
211
|
+
}
|
|
212
|
+
if (attempt <= retryCount && shouldRetryError(classification)) {
|
|
213
|
+
const delay = calculateRetryDelay(
|
|
214
|
+
attempt,
|
|
215
|
+
retryDelay,
|
|
216
|
+
this.config.maxRetryDelay,
|
|
217
|
+
this.config.retryJitter
|
|
218
|
+
);
|
|
219
|
+
if (options.debug) {
|
|
220
|
+
this.logger.debug?.(`[HttpClient] Waiting ${delay}ms before retry ${attempt + 1}`);
|
|
221
|
+
}
|
|
222
|
+
await sleep(delay);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
status: classification.status,
|
|
227
|
+
code: error.response?.status || null,
|
|
228
|
+
error: classification.type,
|
|
229
|
+
headers: error.response?.headers || null,
|
|
230
|
+
data: error.response?.data || null,
|
|
231
|
+
duration,
|
|
232
|
+
retryCount: attempt - 1,
|
|
233
|
+
finalUrl: url
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
status: "unknown",
|
|
239
|
+
code: null,
|
|
240
|
+
error: "unknown",
|
|
241
|
+
headers: null,
|
|
242
|
+
data: null,
|
|
243
|
+
duration: Date.now() - startTime,
|
|
244
|
+
retryCount,
|
|
245
|
+
finalUrl: url
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* GET request
|
|
250
|
+
*/
|
|
251
|
+
async get(url, options = {}) {
|
|
252
|
+
return this.request("GET", url, options);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* POST request
|
|
256
|
+
*/
|
|
257
|
+
async post(url, options = {}) {
|
|
258
|
+
return this.request("POST", url, options);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* PUT request
|
|
262
|
+
*/
|
|
263
|
+
async put(url, options = {}) {
|
|
264
|
+
return this.request("PUT", url, options);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* DELETE request
|
|
268
|
+
*/
|
|
269
|
+
async delete(url, options = {}) {
|
|
270
|
+
return this.request("DELETE", url, options);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* PATCH request
|
|
274
|
+
*/
|
|
275
|
+
async patch(url, options = {}) {
|
|
276
|
+
return this.request("PATCH", url, options);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* HEAD request
|
|
280
|
+
*/
|
|
281
|
+
async head(url, options = {}) {
|
|
282
|
+
return this.request("HEAD", url, options);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* OPTIONS request
|
|
286
|
+
*/
|
|
287
|
+
async options(url, options = {}) {
|
|
288
|
+
return this.request("OPTIONS", url, options);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Map HTTP status code to custom status
|
|
292
|
+
*/
|
|
293
|
+
mapHttpStatusToCustomStatus(httpStatus) {
|
|
294
|
+
if (httpStatus >= 200 && httpStatus < 300) {
|
|
295
|
+
return "success";
|
|
296
|
+
} else if (httpStatus === 401) {
|
|
297
|
+
return "authRequired";
|
|
298
|
+
} else if (httpStatus === 403) {
|
|
299
|
+
return "authFailed";
|
|
300
|
+
} else if (httpStatus >= 400 && httpStatus < 500) {
|
|
301
|
+
return "clientError";
|
|
302
|
+
} else if (httpStatus >= 500) {
|
|
303
|
+
return "serverError";
|
|
304
|
+
}
|
|
305
|
+
return "unknown";
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get current configuration (for debugging)
|
|
309
|
+
*/
|
|
310
|
+
getConfig() {
|
|
311
|
+
return { ...this.config };
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
export {
|
|
315
|
+
HttpClient,
|
|
316
|
+
HttpClientError
|
|
317
|
+
};
|
|
318
|
+
//# sourceMappingURL=http-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http-client/index.ts","../src/http-client/errors.ts","../src/http-client/retry.ts"],"sourcesContent":["/**\n * HttpClient - Resilient HTTP Client with Retry Logic\n *\n * A production-ready HTTP client that:\n * - Wraps axios with enhanced error handling and retry logic\n * - Never throws exceptions - always returns unified response format\n * - Uses exponential backoff with jitter for retries\n * - Provides human-readable error classifications\n * - Supports comprehensive logging\n * - Handles all HTTP methods consistently\n */\n\nimport axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';\nimport type {\n HttpClientConfig,\n RequestOptions,\n HttpClientResponse,\n HttpMethod,\n HttpClientStatus,\n RetryContext\n} from './types.js';\nimport { classifyError, getErrorDescription } from './errors.js';\nimport { calculateRetryDelay, sleep, shouldRetryError, createRetryContext, updateRetryContext } from './retry.js';\n\n/**\n * HttpClient Error class\n */\nexport class HttpClientError extends Error {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = 'HttpClientError';\n }\n}\n\n/**\n * Resilient HTTP Client with automatic retry logic\n */\nexport class HttpClient {\n private axiosInstance: AxiosInstance;\n private config: Required<HttpClientConfig>;\n private logger: any;\n\n constructor(config: HttpClientConfig = {}) {\n this.config = {\n timeout: 30000, // 30 seconds\n retryCount: 3, // 3 retry attempts\n retryDelay: 1000, // 1 second base delay\n maxRetryDelay: 30000, // 30 second max delay\n retryJitter: 0.1, // 10% jitter\n userAgent: 'HttpClient/v1.0',\n validateSSL: true,\n maxRedirects: 5,\n logger: console,\n ...config\n };\n\n this.logger = this.config.logger;\n\n // Create axios instance with base configuration\n this.axiosInstance = axios.create({\n timeout: this.config.timeout,\n validateStatus: () => true, // Never throw on HTTP status codes\n maxRedirects: this.config.maxRedirects,\n headers: {\n 'User-Agent': this.config.userAgent\n },\n // SSL validation\n httpsAgent: this.config.validateSSL ? undefined : {\n rejectUnauthorized: false\n } as any\n });\n\n // Add response interceptor for logging (optional - only if debug enabled)\n this.axiosInstance.interceptors.response.use(\n (response) => response,\n (error) => {\n // Log network-level errors here if needed\n // (HTTP errors are handled in the request method)\n return Promise.reject(error);\n }\n );\n }\n\n /**\n * Make an HTTP request with automatic retry logic\n * Never throws - always returns HttpClientResponse\n */\n async request(\n method: HttpMethod,\n url: string,\n options: RequestOptions = {}\n ): Promise<HttpClientResponse> {\n const startTime = Date.now();\n\n // Merge request options with defaults\n const requestConfig: AxiosRequestConfig = {\n method,\n url,\n timeout: options.timeout || this.config.timeout,\n headers: {\n 'User-Agent': options.userAgent || this.config.userAgent,\n ...options.headers\n },\n params: options.params,\n data: options.data\n };\n\n const retryCount = options.retryCount ?? this.config.retryCount;\n const retryDelay = options.retryDelay ?? this.config.retryDelay;\n\n // Initialize retry context\n let retryContext: RetryContext | null = null;\n let lastError: any = null;\n\n // Attempt the request with retries\n for (let attempt = 1; attempt <= retryCount + 1; attempt++) {\n try {\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} (attempt ${attempt}/${retryCount + 1})`);\n }\n\n const response: AxiosResponse = await this.axiosInstance.request(requestConfig);\n const duration = Date.now() - startTime;\n\n // Success! Return unified response format\n const customStatus = this.mapHttpStatusToCustomStatus(response.status);\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} → ${response.status} ${customStatus} (${duration}ms)`);\n }\n\n return {\n status: customStatus,\n code: response.status,\n headers: response.headers as Record<string, string>,\n data: response.data,\n duration,\n retryCount: attempt - 1,\n finalUrl: response.request?.res?.responseUrl || url\n };\n\n } catch (error: any) {\n lastError = error;\n const duration = Date.now() - startTime;\n\n // Classify the error\n const classification = classifyError(error);\n const errorDescription = getErrorDescription(classification.type);\n\n // Log the error\n if (classification.retryable && attempt <= retryCount) {\n this.logger.warn?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}. Retrying in ${retryDelay}ms...`);\n } else if (!classification.retryable || attempt > retryCount) {\n this.logger.error?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}`);\n }\n\n // Check if we should retry\n if (attempt <= retryCount && shouldRetryError(classification)) {\n // Calculate delay and wait\n const delay = calculateRetryDelay(\n attempt,\n retryDelay,\n this.config.maxRetryDelay,\n this.config.retryJitter\n );\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] Waiting ${delay}ms before retry ${attempt + 1}`);\n }\n\n await sleep(delay);\n continue;\n }\n\n // No more retries or not retryable - return error response\n return {\n status: classification.status,\n code: error.response?.status || null,\n error: classification.type,\n headers: error.response?.headers || null,\n data: error.response?.data || null,\n duration,\n retryCount: attempt - 1,\n finalUrl: url\n };\n }\n }\n\n // This should never be reached, but just in case\n return {\n status: 'unknown',\n code: null,\n error: 'unknown',\n headers: null,\n data: null,\n duration: Date.now() - startTime,\n retryCount: retryCount,\n finalUrl: url\n };\n }\n\n /**\n * GET request\n */\n async get(url: string, options: RequestOptions = {}): Promise<HttpClientResponse> {\n return this.request('GET', url, options);\n }\n\n /**\n * POST request\n */\n async post(url: string, options: RequestOptions = {}): Promise<HttpClientResponse> {\n return this.request('POST', url, options);\n }\n\n /**\n * PUT request\n */\n async put(url: string, options: RequestOptions = {}): Promise<HttpClientResponse> {\n return this.request('PUT', url, options);\n }\n\n /**\n * DELETE request\n */\n async delete(url: string, options: RequestOptions = {}): Promise<HttpClientResponse> {\n return this.request('DELETE', url, options);\n }\n\n /**\n * PATCH request\n */\n async patch(url: string, options: RequestOptions = {}): Promise<HttpClientResponse> {\n return this.request('PATCH', url, options);\n }\n\n /**\n * HEAD request\n */\n async head(url: string, options: RequestOptions = {}): Promise<HttpClientResponse> {\n return this.request('HEAD', url, options);\n }\n\n /**\n * OPTIONS request\n */\n async options(url: string, options: RequestOptions = {}): Promise<HttpClientResponse> {\n return this.request('OPTIONS', url, options);\n }\n\n /**\n * Map HTTP status code to custom status\n */\n private mapHttpStatusToCustomStatus(httpStatus: number): HttpClientStatus {\n if (httpStatus >= 200 && httpStatus < 300) {\n return 'success';\n } else if (httpStatus === 401) {\n return 'authRequired';\n } else if (httpStatus === 403) {\n return 'authFailed';\n } else if (httpStatus >= 400 && httpStatus < 500) {\n return 'clientError';\n } else if (httpStatus >= 500) {\n return 'serverError';\n }\n return 'unknown';\n }\n\n /**\n * Get current configuration (for debugging)\n */\n getConfig(): Readonly<HttpClientConfig> {\n return { ...this.config };\n }\n}\n","/**\n * HttpClient Error Classification\n *\n * Maps technical errors to human-readable, use-case oriented error types\n */\n\nimport type { HttpClientErrorType, HttpClientStatus, ErrorClassification } from './types.js';\n\n/**\n * Classify an error and determine retry behavior\n */\nexport function classifyError(error: any): ErrorClassification {\n // Handle axios response errors (HTTP errors)\n if (error.response) {\n const { status } = error.response;\n\n switch (status) {\n case 400:\n return { type: 'badRequest', retryable: false, isAuth: false, status: 'clientError' };\n case 401:\n return { type: 'unauthorized', retryable: false, isAuth: true, status: 'authRequired' };\n case 403:\n return { type: 'forbidden', retryable: false, isAuth: true, status: 'authFailed' };\n case 404:\n return { type: 'notFound', retryable: false, isAuth: false, status: 'clientError' };\n case 405:\n return { type: 'methodNotAllowed', retryable: false, isAuth: false, status: 'clientError' };\n case 409:\n return { type: 'conflict', retryable: false, isAuth: false, status: 'clientError' };\n case 422:\n return { type: 'unprocessableEntity', retryable: false, isAuth: false, status: 'clientError' };\n case 429:\n return { type: 'tooManyRequests', retryable: true, isAuth: false, status: 'clientError' };\n case 500:\n return { type: 'internalServerError', retryable: true, isAuth: false, status: 'serverError' };\n case 502:\n return { type: 'badGateway', retryable: true, isAuth: false, status: 'serverError' };\n case 503:\n return { type: 'serviceUnavailable', retryable: true, isAuth: false, status: 'serverError' };\n case 504:\n return { type: 'gatewayTimeout', retryable: true, isAuth: false, status: 'serverError' };\n default:\n if (status >= 400 && status < 500) {\n return { type: 'clientError' as HttpClientErrorType, retryable: false, isAuth: false, status: 'clientError' };\n } else if (status >= 500) {\n return { type: 'serverError' as HttpClientErrorType, retryable: true, isAuth: false, status: 'serverError' };\n }\n break;\n }\n }\n\n // Handle network/connection errors (no response)\n if (error.code) {\n switch (error.code) {\n case 'ECONNREFUSED':\n case 'ECONNRESET':\n case 'EPIPE':\n case 'ENOTFOUND':\n case 'EHOSTUNREACH':\n case 'ENETUNREACH':\n return { type: 'connectionFailed', retryable: true, isAuth: false, status: 'networkError' };\n\n case 'ETIMEDOUT':\n case 'ECONNABORTED':\n case 'ESOCKETTIMEDOUT':\n return { type: 'timeout', retryable: true, isAuth: false, status: 'timeout' };\n\n case 'EAUTH':\n case 'EACCES':\n return { type: 'unauthorized', retryable: false, isAuth: true, status: 'authRequired' };\n\n default:\n return { type: 'networkError', retryable: true, isAuth: false, status: 'networkError' };\n }\n }\n\n // Handle timeout errors\n if (error.message && (\n error.message.includes('timeout') ||\n error.message.includes('TIMEOUT') ||\n error.message.includes('aborted')\n )) {\n return { type: 'timeout', retryable: true, isAuth: false, status: 'timeout' };\n }\n\n // Handle cancellation\n if (error.name === 'AbortError' || error.message?.includes('cancelled')) {\n return { type: 'requestCancelled', retryable: false, isAuth: false, status: 'unknown' };\n }\n\n // Default fallback\n return { type: 'unknown', retryable: false, isAuth: false, status: 'unknown' };\n}\n\n/**\n * Get a human-readable description for an error type\n */\nexport function getErrorDescription(errorType: HttpClientErrorType): string {\n const descriptions: Record<HttpClientErrorType, string> = {\n connectionFailed: 'Failed to establish a connection to the server',\n timeout: 'Request timed out before completing',\n networkError: 'Network connection issue occurred',\n badRequest: 'Request was malformed or invalid',\n unauthorized: 'Authentication credentials are required',\n forbidden: 'Access to the requested resource is forbidden',\n notFound: 'The requested resource was not found',\n methodNotAllowed: 'HTTP method not allowed for this resource',\n conflict: 'Request conflicts with current server state',\n unprocessableEntity: 'Request data could not be processed',\n tooManyRequests: 'Too many requests sent in a short time',\n internalServerError: 'Server encountered an internal error',\n badGateway: 'Invalid response from upstream server',\n serviceUnavailable: 'Server is temporarily unavailable',\n gatewayTimeout: 'Upstream server timed out',\n unknown: 'An unknown error occurred',\n requestCancelled: 'Request was cancelled before completion'\n };\n\n return descriptions[errorType] || 'An unknown error occurred';\n}\n","/**\n * HttpClient Retry Logic\n *\n * Implements exponential backoff with jitter to prevent thundering herd problems\n */\n\nimport type { RetryContext } from './types.js';\n\n/**\n * Calculate the next retry delay using exponential backoff with jitter\n *\n * Formula: delay = baseDelay * (2 ^ (attempt - 1)) + randomJitter\n *\n * Jitter prevents the \"thundering herd\" problem where multiple failed requests\n * all retry at the exact same time, overwhelming the server.\n */\nexport function calculateRetryDelay(\n attempt: number,\n baseDelay: number,\n maxDelay: number,\n jitterFactor: number = 0.1\n): number {\n // Exponential backoff: baseDelay * (2 ^ (attempt - 1))\n const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);\n\n // Cap at maximum delay\n const cappedDelay = Math.min(exponentialDelay, maxDelay);\n\n // Add jitter: random variation up to jitterFactor of the delay\n const jitter = cappedDelay * jitterFactor * Math.random();\n\n return Math.floor(cappedDelay + jitter);\n}\n\n/**\n * Sleep for the specified number of milliseconds\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Determine if an error should be retried based on classification\n */\nexport function shouldRetryError(classification: { retryable: boolean; isAuth: boolean }): boolean {\n // Never retry auth errors (they won't succeed with the same credentials)\n if (classification.isAuth) {\n return false;\n }\n\n // Retry if the error is classified as retryable\n return classification.retryable;\n}\n\n/**\n * Create a retry context for tracking retry attempts\n */\nexport function createRetryContext(\n maxAttempts: number,\n baseDelay: number,\n maxDelay: number,\n jitterFactor: number\n): RetryContext {\n return {\n attempt: 1,\n maxAttempts,\n lastError: new Error('Initial attempt'),\n totalDelay: 0,\n nextDelay: calculateRetryDelay(1, baseDelay, maxDelay, jitterFactor)\n };\n}\n\n/**\n * Update retry context for the next attempt\n */\nexport function updateRetryContext(\n context: RetryContext,\n lastError: Error,\n baseDelay: number,\n maxDelay: number,\n jitterFactor: number\n): RetryContext {\n const nextAttempt = context.attempt + 1;\n const nextDelay = calculateRetryDelay(nextAttempt, baseDelay, maxDelay, jitterFactor);\n\n return {\n attempt: nextAttempt,\n maxAttempts: context.maxAttempts,\n lastError,\n totalDelay: context.totalDelay + context.nextDelay,\n nextDelay\n };\n}\n"],"mappings":"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","names":[]}
|