@aligent/microservice-util-lib 1.3.2 → 1.3.3

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.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "A set of utility functions for Aligent Microservices",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -15,7 +15,7 @@
15
15
  "@aws-sdk/client-ssm": "^3.922.0",
16
16
  "oauth-sign": "^0.9.0",
17
17
  "object-hash": "^3.0.0",
18
- "openapi-fetch": "^0.15.0"
18
+ "openapi-fetch": "^0.17.0"
19
19
  },
20
20
  "author": "Aligent",
21
21
  "license": "MIT",
package/src/index.d.ts CHANGED
@@ -3,9 +3,10 @@ import fetchSsmParams from './fetch-ssm-params/fetch-ssm-params';
3
3
  import getAwsIdFromArn from './get-aws-id-from-arn/get-aws-id-from-arn';
4
4
  import hasDefinedProperties from './has-properties-defined/has-properties-defined';
5
5
  import { ApiKey, Basic, OAuth10a, OAuth20, apiKeyAuthMiddleware, basicAuthMiddleware, oAuth10aAuthMiddleware, oAuth20AuthMiddleware } from './openapi-fetch-middlewares/authentications';
6
+ import { LogLevel, Logger, logMiddleware } from './openapi-fetch-middlewares/log';
6
7
  import { RetryConfig as RetryMiddlewareConfig, retryMiddleware } from './openapi-fetch-middlewares/retry';
7
8
  import remap, { ObjectMap, Remap } from './remap/remap';
8
9
  import retryWrapper, { RetryConfig } from './retry-wrapper/retry-wrapper';
9
10
  import S3Dao from './s3/s3';
10
- export type { ApiKey, Basic, OAuth10a, OAuth20, ObjectMap, Remap, RetryConfig, RetryMiddlewareConfig, S3Dao, };
11
- export { apiKeyAuthMiddleware, basicAuthMiddleware, chunkBy, fetchSsmParams, getAwsIdFromArn, hasDefinedProperties, oAuth10aAuthMiddleware, oAuth20AuthMiddleware, remap, retryMiddleware, retryWrapper, };
11
+ export type { ApiKey, Basic, LogLevel, Logger, OAuth10a, OAuth20, ObjectMap, Remap, RetryConfig, RetryMiddlewareConfig, S3Dao, };
12
+ export { apiKeyAuthMiddleware, basicAuthMiddleware, chunkBy, fetchSsmParams, getAwsIdFromArn, hasDefinedProperties, logMiddleware, oAuth10aAuthMiddleware, oAuth20AuthMiddleware, remap, retryMiddleware, retryWrapper, };
package/src/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.retryWrapper = exports.retryMiddleware = exports.remap = exports.oAuth20AuthMiddleware = exports.oAuth10aAuthMiddleware = exports.hasDefinedProperties = exports.getAwsIdFromArn = exports.fetchSsmParams = exports.chunkBy = exports.basicAuthMiddleware = exports.apiKeyAuthMiddleware = void 0;
6
+ exports.retryWrapper = exports.retryMiddleware = exports.remap = exports.oAuth20AuthMiddleware = exports.oAuth10aAuthMiddleware = exports.logMiddleware = exports.hasDefinedProperties = exports.getAwsIdFromArn = exports.fetchSsmParams = exports.chunkBy = exports.basicAuthMiddleware = exports.apiKeyAuthMiddleware = void 0;
7
7
  /* v8 ignore start */
8
8
  const chunk_by_1 = __importDefault(require("./chunk-by/chunk-by"));
9
9
  exports.chunkBy = chunk_by_1.default;
@@ -18,6 +18,8 @@ Object.defineProperty(exports, "apiKeyAuthMiddleware", { enumerable: true, get:
18
18
  Object.defineProperty(exports, "basicAuthMiddleware", { enumerable: true, get: function () { return authentications_1.basicAuthMiddleware; } });
19
19
  Object.defineProperty(exports, "oAuth10aAuthMiddleware", { enumerable: true, get: function () { return authentications_1.oAuth10aAuthMiddleware; } });
20
20
  Object.defineProperty(exports, "oAuth20AuthMiddleware", { enumerable: true, get: function () { return authentications_1.oAuth20AuthMiddleware; } });
21
+ const log_1 = require("./openapi-fetch-middlewares/log");
22
+ Object.defineProperty(exports, "logMiddleware", { enumerable: true, get: function () { return log_1.logMiddleware; } });
21
23
  const retry_1 = require("./openapi-fetch-middlewares/retry");
22
24
  Object.defineProperty(exports, "retryMiddleware", { enumerable: true, get: function () { return retry_1.retryMiddleware; } });
23
25
  const remap_1 = __importDefault(require("./remap/remap"));
@@ -0,0 +1,56 @@
1
+ import type { Middleware } from 'openapi-fetch';
2
+ /**
3
+ * Logger interface to support various logging implementations
4
+ * (console, winston, pino, bunyan, etc.)
5
+ */
6
+ interface Logger {
7
+ info(message: string, ...args: unknown[]): void;
8
+ debug?(message: string, ...args: unknown[]): void;
9
+ }
10
+ type LogLevel = 'INFO' | 'DEBUG';
11
+ /**
12
+ * Creates a logging middleware for openapi-fetch clients.
13
+ *
14
+ * This middleware logs HTTP requests and responses with Content-Type handling.
15
+ * It supports various logger implementations and configurable log levels (INFO or DEBUG).
16
+ *
17
+ * Features:
18
+ * - Automatic Content-Type detection and appropriate parsing
19
+ * - Support for JSON, text, XML, form data, and binary content
20
+ * - Configurable log levels (INFO/DEBUG)
21
+ * - Compatible with multiple logging libraries
22
+ *
23
+ * @param clientName - A descriptive name for the API client (used in log messages)
24
+ * @param logLevel - The logging level to use: 'INFO' (default) or 'DEBUG'
25
+ * @param logger - Logger instance implementing the Logger interface (defaults to console)
26
+ * @returns An openapi-fetch middleware that logs requests and responses
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // Basic usage with default console logger at INFO level
31
+ * import createClient from 'openapi-fetch';
32
+ *
33
+ * const client = createClient({ baseUrl: 'https://api.example.com' });
34
+ * client.use(logMiddleware('MyAPI'));
35
+ * ```
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // Using DEBUG level with console
40
+ * client.use(logMiddleware('MyAPI', 'DEBUG'));
41
+ * ```
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * // Custom logger implementation
46
+ * const customLogger = {
47
+ * info: (msg, ...args) => console.log('[INFO]', msg, ...args),
48
+ * debug: (msg, ...args) => console.log('[DEBUG]', msg, ...args)
49
+ * };
50
+ *
51
+ * client.use(logMiddleware('MyAPI', 'DEBUG', customLogger));
52
+ * ```
53
+ */
54
+ declare function logMiddleware(clientName: string, logLevel?: LogLevel, logger?: Logger): Middleware;
55
+ export { logMiddleware };
56
+ export type { Logger, LogLevel };
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logMiddleware = logMiddleware;
4
+ /**
5
+ * Parse request/response body based on Content-Type header
6
+ */
7
+ async function parseBody(source, contentType) {
8
+ const normalizedContentType = contentType ? contentType.toLowerCase() : 'application/json';
9
+ try {
10
+ // JSON content
11
+ if (normalizedContentType.includes('application/json')) {
12
+ return await source.clone().json();
13
+ }
14
+ // Text content
15
+ if (normalizedContentType.includes('text/') ||
16
+ normalizedContentType.includes('application/xml') ||
17
+ normalizedContentType.includes('application/x-www-form-urlencoded')) {
18
+ return await source.clone().text();
19
+ }
20
+ // Binary or multipart content - don't parse
21
+ if (normalizedContentType.includes('multipart/form-data') ||
22
+ normalizedContentType.includes('application/octet-stream') ||
23
+ normalizedContentType.includes('image/') ||
24
+ normalizedContentType.includes('video/') ||
25
+ normalizedContentType.includes('audio/')) {
26
+ return `[Binary content: ${contentType}]`;
27
+ }
28
+ // Unknown content type, try JSON as default
29
+ return await source.clone().json();
30
+ }
31
+ catch (error) {
32
+ return `[Unable to parse ${contentType} body: ${error instanceof Error ? error.message : 'Unknown error'}]`;
33
+ }
34
+ }
35
+ /**
36
+ * Creates a logging middleware for openapi-fetch clients.
37
+ *
38
+ * This middleware logs HTTP requests and responses with Content-Type handling.
39
+ * It supports various logger implementations and configurable log levels (INFO or DEBUG).
40
+ *
41
+ * Features:
42
+ * - Automatic Content-Type detection and appropriate parsing
43
+ * - Support for JSON, text, XML, form data, and binary content
44
+ * - Configurable log levels (INFO/DEBUG)
45
+ * - Compatible with multiple logging libraries
46
+ *
47
+ * @param clientName - A descriptive name for the API client (used in log messages)
48
+ * @param logLevel - The logging level to use: 'INFO' (default) or 'DEBUG'
49
+ * @param logger - Logger instance implementing the Logger interface (defaults to console)
50
+ * @returns An openapi-fetch middleware that logs requests and responses
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * // Basic usage with default console logger at INFO level
55
+ * import createClient from 'openapi-fetch';
56
+ *
57
+ * const client = createClient({ baseUrl: 'https://api.example.com' });
58
+ * client.use(logMiddleware('MyAPI'));
59
+ * ```
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // Using DEBUG level with console
64
+ * client.use(logMiddleware('MyAPI', 'DEBUG'));
65
+ * ```
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // Custom logger implementation
70
+ * const customLogger = {
71
+ * info: (msg, ...args) => console.log('[INFO]', msg, ...args),
72
+ * debug: (msg, ...args) => console.log('[DEBUG]', msg, ...args)
73
+ * };
74
+ *
75
+ * client.use(logMiddleware('MyAPI', 'DEBUG', customLogger));
76
+ * ```
77
+ */
78
+ function logMiddleware(clientName, logLevel = 'INFO', logger = console) {
79
+ const log = (message, ...args) => {
80
+ if (logLevel === 'DEBUG' && logger.debug) {
81
+ logger.debug(message, ...args);
82
+ return;
83
+ }
84
+ logger.info(message, ...args);
85
+ };
86
+ return {
87
+ async onRequest({ options, params, request }) {
88
+ const contentType = request.headers.get('Content-Type');
89
+ log(`${request.method} request to ${clientName}`, {
90
+ method: request.method,
91
+ baseUrl: options.baseUrl,
92
+ url: request.url,
93
+ params: params,
94
+ body: await parseBody(request, contentType),
95
+ });
96
+ },
97
+ async onResponse({ response }) {
98
+ const contentType = response.headers.get('Content-Type');
99
+ log(`Response from ${clientName}`, {
100
+ status: response.status,
101
+ body: await parseBody(response, contentType),
102
+ });
103
+ },
104
+ };
105
+ }
@@ -140,8 +140,14 @@ function shouldGenerateBodyHash(body, method, includeBodyHash) {
140
140
  async function generateOauthParams(request, options, params, config) {
141
141
  const { algorithm, includeBodyHash = 'auto', realm, callback, verifier } = config;
142
142
  const { consumerKey, consumerSecret, token, tokenSecret } = await config.credentials();
143
- const method = (request.method || 'GET').toUpperCase();
144
- const url = combineUrlAndPathParams(request.url, params.path);
143
+ // Due to the way that openapi-fetch & fetch API are implemented in NodeJS (undici),
144
+ // each request only can be used ONCE. We have to clone the request like this to extract information
145
+ // Otherwise, undici will not be able to create & send the request.
146
+ const clonedRequest = request.clone();
147
+ const method = (clonedRequest.method || 'GET').toUpperCase();
148
+ const url = combineUrlAndPathParams(clonedRequest.url, params.path);
149
+ const contentType = clonedRequest.headers.get('Content-Type');
150
+ const body = await clonedRequest.text();
145
151
  const oauthParams = {
146
152
  oauth_consumer_key: consumerKey,
147
153
  oauth_nonce: crypto_1.default.randomUUID(),
@@ -165,22 +171,18 @@ async function generateOauthParams(request, options, params, config) {
165
171
  if (params.query) {
166
172
  addParamsToSign(paramsToSign, params.query);
167
173
  }
168
- const body = await request.text();
169
174
  // If user submit a form, then include form parameters in the
170
175
  // signature as parameters rather than the body hash
171
- if (request.headers.get('Content-Type') === 'application/x-www-form-urlencoded') {
176
+ if (contentType === 'application/x-www-form-urlencoded') {
172
177
  addParamsToSign(paramsToSign, new URLSearchParams(body));
173
- console.log(JSON.stringify(paramsToSign));
174
178
  }
175
- else {
176
- if (shouldGenerateBodyHash(body, method, includeBodyHash)) {
177
- const bodyHash = crypto_1.default
178
- .createHash(algorithm === 'HMAC-SHA1' ? 'sha1' : 'sha256')
179
- .update(Buffer.from(body))
180
- .digest('base64');
181
- oauthParams.oauth_body_hash = bodyHash;
182
- addParamToSign(paramsToSign, 'oauth_body_hash', bodyHash);
183
- }
179
+ else if (shouldGenerateBodyHash(body, method, includeBodyHash)) {
180
+ const bodyHash = crypto_1.default
181
+ .createHash(algorithm === 'HMAC-SHA1' ? 'sha1' : 'sha256')
182
+ .update(Buffer.from(body))
183
+ .digest('base64');
184
+ oauthParams.oauth_body_hash = bodyHash;
185
+ addParamToSign(paramsToSign, 'oauth_body_hash', bodyHash);
184
186
  }
185
187
  const oauthUrl = getOAuthUrl(options.baseUrl, url);
186
188
  oauthParams.oauth_signature = (0, oauth_sign_1.sign)(algorithm, method, oauthUrl, paramsToSign, consumerSecret, tokenSecret);
@@ -0,0 +1 @@
1
+ {"version":"5.9.3"}