@aligent/microservice-util-lib 1.2.2 → 1.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aligent/microservice-util-lib",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "A set of utility functions for Aligent Microservices",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -1,5 +1,6 @@
1
1
  import type { Middleware } from 'openapi-fetch';
2
2
  import type { RetryConfig, RetryContext, RetryDelayFn } from './types/retry';
3
+ import { HttpResponseError, isHttpResponseError } from './utils/http-response-error';
3
4
  /**
4
5
  * This middleware implements retry logic with support for:
5
6
  * - Configurable number of retry attempts
@@ -50,5 +51,5 @@ import type { RetryConfig, RetryContext, RetryDelayFn } from './types/retry';
50
51
  * });
51
52
  */
52
53
  declare function retryMiddleware(config?: RetryConfig): Middleware;
53
- export { retryMiddleware };
54
+ export { HttpResponseError, isHttpResponseError, retryMiddleware };
54
55
  export type { RetryConfig, RetryContext, RetryDelayFn };
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isHttpResponseError = exports.HttpResponseError = void 0;
3
4
  exports.retryMiddleware = retryMiddleware;
5
+ const http_response_error_1 = require("./utils/http-response-error");
6
+ Object.defineProperty(exports, "HttpResponseError", { enumerable: true, get: function () { return http_response_error_1.HttpResponseError; } });
7
+ Object.defineProperty(exports, "isHttpResponseError", { enumerable: true, get: function () { return http_response_error_1.isHttpResponseError; } });
4
8
  const is_network_error_1 = require("./utils/is-network-error");
5
9
  const IDEMPOTENT_HTTP_METHODS = ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'];
6
10
  /**
@@ -85,14 +89,15 @@ function shouldRetryOnStatus(status, retryOn) {
85
89
  return retryOn.includes(status);
86
90
  }
87
91
  /**
88
- * Checks the response status and throw error if it's not "ok".
92
+ * Checks the response status and throws HttpResponseError if it's not "ok".
89
93
  *
90
94
  * @param {Response} response - The HTTP response object.
91
- * @throws {Error} When the response is not an "ok" response.
95
+ * @param {Request} request - The HTTP request object.
96
+ * @throws {HttpResponseError} When the response is not an "ok" response.
92
97
  */
93
- function throwErrorIfNotOkResponse(response) {
98
+ function throwErrorIfNotOkResponse(response, request) {
94
99
  if (!response.ok) {
95
- throw new Error(`${response.status}: ${response.statusText}`);
100
+ throw new http_response_error_1.HttpResponseError(response, request);
96
101
  }
97
102
  }
98
103
  /**
@@ -158,17 +163,17 @@ function retryMiddleware(config) {
158
163
  const context = { attempt: 1, request, response, error: null };
159
164
  // If retryOn is specified, only use that list
160
165
  if (config?.retryOn && config.retryOn.length > 0) {
161
- if (!shouldRetryOnStatus(context.response.status, config.retryOn)) {
162
- throwErrorIfNotOkResponse(context.response);
163
- return context.response;
166
+ if (!shouldRetryOnStatus(response.status, config.retryOn)) {
167
+ throwErrorIfNotOkResponse(response, request);
168
+ return response;
164
169
  }
165
170
  return await performRetries(normalisedConfig, context);
166
171
  }
167
172
  // Otherwise, check if we should retry based on retry condition
168
173
  const shouldRetry = await normalisedConfig.retryCondition(context, normalisedConfig.idempotentOnly);
169
174
  if (!shouldRetry) {
170
- throwErrorIfNotOkResponse(context.response);
171
- return context.response;
175
+ throwErrorIfNotOkResponse(response, request);
176
+ return response;
172
177
  }
173
178
  return await performRetries(normalisedConfig, context);
174
179
  },
@@ -199,8 +204,10 @@ async function performRetries(config, context) {
199
204
  await config.onRetry({ ...context, attempt, response });
200
205
  }
201
206
  try {
202
- const signal = config.shouldResetTimeout ? undefined : context.request.signal;
203
- response = await config.fetch(new Request(context.request, { signal }));
207
+ const request = config.shouldResetTimeout
208
+ ? new Request(context.request)
209
+ : new Request(context.request, { signal: context.request.signal });
210
+ response = await config.fetch(request);
204
211
  context = { ...context, attempt: attempt + 1, response, error: null };
205
212
  attempt++;
206
213
  }
@@ -214,18 +221,18 @@ async function performRetries(config, context) {
214
221
  continue;
215
222
  }
216
223
  if (config.retryOn && !shouldRetryOnStatus(response?.status, config.retryOn)) {
217
- throwErrorIfNotOkResponse(response);
224
+ throwErrorIfNotOkResponse(response, context.request);
218
225
  return response;
219
226
  }
220
227
  const shouldRetry = await config.retryCondition(context, config.idempotentOnly);
221
228
  if (!shouldRetry) {
222
- throwErrorIfNotOkResponse(response);
229
+ throwErrorIfNotOkResponse(response, context.request);
223
230
  return response;
224
231
  }
225
232
  } while (attempt <= maxRetries);
226
233
  if (!response) {
227
234
  throw context.error;
228
235
  }
229
- throwErrorIfNotOkResponse(response);
236
+ throwErrorIfNotOkResponse(response, context.request);
230
237
  return response;
231
238
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Custom error class for HTTP response errors, similar to Axios Error.
3
+ * Provides detailed information about the failed request and response.
4
+ */
5
+ export declare class HttpResponseError extends Error {
6
+ readonly name = "HttpResponseError";
7
+ readonly status: number;
8
+ readonly statusText: string;
9
+ readonly response: Response;
10
+ readonly request: Request;
11
+ readonly isHttpResponseError = true;
12
+ constructor(response: Response, request: Request);
13
+ /**
14
+ * Returns a JSON representation of the error for logging/debugging.
15
+ */
16
+ toJson(): {
17
+ name: string;
18
+ message: string;
19
+ status: number;
20
+ statusText: string;
21
+ method: string;
22
+ url: string;
23
+ };
24
+ }
25
+ /**
26
+ * Type guard to check if an error is an HttpResponseError.
27
+ * Useful for narrowing error types in catch blocks.
28
+ *
29
+ * @param {unknown} error - The error to check.
30
+ * @returns {boolean} True if the error is an HttpResponseError.
31
+ *
32
+ * @example
33
+ * try {
34
+ * await fetchData();
35
+ * } catch (error) {
36
+ * if (isHttpResponseError(error)) {
37
+ * console.log(`Request failed with status ${error.status}`);
38
+ * console.log(`URL: ${error.request.url}`);
39
+ * }
40
+ * }
41
+ */
42
+ export declare function isHttpResponseError(error: unknown): error is HttpResponseError;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpResponseError = void 0;
4
+ exports.isHttpResponseError = isHttpResponseError;
5
+ /**
6
+ * Custom error class for HTTP response errors, similar to Axios Error.
7
+ * Provides detailed information about the failed request and response.
8
+ */
9
+ class HttpResponseError extends Error {
10
+ name = 'HttpResponseError';
11
+ status;
12
+ statusText;
13
+ response;
14
+ request;
15
+ isHttpResponseError = true;
16
+ constructor(response, request) {
17
+ super(`${response.status}: ${response.statusText}`);
18
+ this.status = response.status;
19
+ this.statusText = response.statusText;
20
+ this.response = response;
21
+ this.request = request;
22
+ // Maintains proper stack trace for where error was thrown (V8 engines)
23
+ if (Error.captureStackTrace) {
24
+ Error.captureStackTrace(this, HttpResponseError);
25
+ }
26
+ }
27
+ /**
28
+ * Returns a JSON representation of the error for logging/debugging.
29
+ */
30
+ toJson() {
31
+ return {
32
+ name: this.name,
33
+ message: this.message,
34
+ status: this.status,
35
+ statusText: this.statusText,
36
+ method: this.request.method,
37
+ url: this.request.url,
38
+ };
39
+ }
40
+ }
41
+ exports.HttpResponseError = HttpResponseError;
42
+ /**
43
+ * Type guard to check if an error is an HttpResponseError.
44
+ * Useful for narrowing error types in catch blocks.
45
+ *
46
+ * @param {unknown} error - The error to check.
47
+ * @returns {boolean} True if the error is an HttpResponseError.
48
+ *
49
+ * @example
50
+ * try {
51
+ * await fetchData();
52
+ * } catch (error) {
53
+ * if (isHttpResponseError(error)) {
54
+ * console.log(`Request failed with status ${error.status}`);
55
+ * console.log(`URL: ${error.request.url}`);
56
+ * }
57
+ * }
58
+ */
59
+ function isHttpResponseError(error) {
60
+ return (error instanceof HttpResponseError ||
61
+ (typeof error === 'object' &&
62
+ error !== null &&
63
+ 'isHttpResponseError' in error &&
64
+ error.isHttpResponseError === true));
65
+ }
@@ -33,7 +33,11 @@ type GetKeyType<Key extends string, O extends {
33
33
  * Given an ObjectMap, return a new ObjectMap with the first index of each
34
34
  * tuple replaced with the value of the second index
35
35
  */
36
- type OverrideIndex<M extends ObjectMap[number], V extends string> = readonly [M[0], V, M[2]];
36
+ type OverrideIndex<M extends ObjectMap[number], V extends string> = M extends readonly [
37
+ unknown,
38
+ unknown,
39
+ infer T
40
+ ] ? readonly [M[0], V, T] : readonly [M[0], V];
37
41
  /**
38
42
  * Given a key and a base object, return the type of the property referencable
39
43
  * by the key on the base object
@@ -48,7 +52,7 @@ type OverrideIndex<M extends ObjectMap[number], V extends string> = readonly [M[
48
52
  */
49
53
  type ConstructTypeFromPropertiesInternal<M extends ObjectMap[number], O extends {
50
54
  [key: string]: any;
51
- }> = M[1] extends `${infer P}.${infer Rest}` ? {
55
+ }> = M[1] extends `${infer P extends string}.${infer Rest extends string}` ? {
52
56
  [key in P]: ConstructTypeFromPropertiesInternal<OverrideIndex<M, Rest>, O>;
53
57
  } : {
54
58
  [key in M[1]]: GetKeyType<M[0], O, M>;