@aligent/microservice-util-lib 1.4.0 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aligent/microservice-util-lib",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "A set of utility functions for Aligent Microservices",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
package/src/index.d.ts CHANGED
@@ -2,11 +2,11 @@ import chunkBy from './chunk-by/chunk-by';
2
2
  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
- import { ApiKey, Basic, OAuth10a, OAuth20, apiKeyAuthMiddleware, basicAuthMiddleware, oAuth10aAuthMiddleware, oAuth20AuthMiddleware } from './openapi-fetch-middlewares/authentications';
5
+ import { ApiKey, Basic, OAuth10a, OAuth20, apiKeyAuthMiddleware, basicAuthMiddleware, oAuth10aAuthMiddleware, oAuth20AuthMiddleware, resignOauth10aRequest } from './openapi-fetch-middlewares/authentications';
6
6
  import { LogLevel, Logger, logMiddleware } from './openapi-fetch-middlewares/log';
7
7
  import { RetryConfig as RetryMiddlewareConfig, retryMiddleware } from './openapi-fetch-middlewares/retry';
8
8
  import remap, { ObjectMap, Remap } from './remap/remap';
9
9
  import retryWrapper, { RetryConfig } from './retry-wrapper/retry-wrapper';
10
10
  import S3Dao from './s3/s3';
11
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, };
12
+ export { apiKeyAuthMiddleware, basicAuthMiddleware, chunkBy, fetchSsmParams, getAwsIdFromArn, hasDefinedProperties, logMiddleware, oAuth10aAuthMiddleware, oAuth20AuthMiddleware, remap, resignOauth10aRequest, 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.logMiddleware = exports.hasDefinedProperties = exports.getAwsIdFromArn = exports.fetchSsmParams = exports.chunkBy = exports.basicAuthMiddleware = exports.apiKeyAuthMiddleware = void 0;
6
+ exports.retryWrapper = exports.retryMiddleware = exports.resignOauth10aRequest = 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,7 @@ 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
+ Object.defineProperty(exports, "resignOauth10aRequest", { enumerable: true, get: function () { return authentications_1.resignOauth10aRequest; } });
21
22
  const log_1 = require("./openapi-fetch-middlewares/log");
22
23
  Object.defineProperty(exports, "logMiddleware", { enumerable: true, get: function () { return log_1.logMiddleware; } });
23
24
  const retry_1 = require("./openapi-fetch-middlewares/retry");
@@ -1,5 +1,14 @@
1
1
  import type { Middleware } from 'openapi-fetch';
2
- import type { ApiKey, Basic, OAuth10a, OAuth20 } from './types/authentications';
2
+ import { resignOauth10aRequest } from './oauth10a/oauth10a';
3
+ import type { ApiKey, Basic, OAuth10a, OAuth20, Resolvable } from './types/authentications';
4
+ /**
5
+ * Resolves a `Resolvable<T>` value to its underlying type.
6
+ * If the value is a function, it is called and awaited; otherwise, it is returned as-is.
7
+ *
8
+ * @param {Resolvable<T>} value - The resolvable value.
9
+ * @returns {Promise<T>} The resolved value.
10
+ */
11
+ declare function resolve<T>(value: Resolvable<T>): Promise<T>;
3
12
  /**
4
13
  * Creates an openapi-fetch middleware for API key authentication.
5
14
  * This middleware sets the API key in the specified header for each request.
@@ -8,9 +17,17 @@ import type { ApiKey, Basic, OAuth10a, OAuth20 } from './types/authentications';
8
17
  * @returns {Middleware} The middleware for API key authentication.
9
18
  *
10
19
  * @example
20
+ * // Static value
11
21
  * const middleware = apiKeyAuthMiddleware({
12
22
  * header: 'x-api-key',
13
- * value: async () => 'your-api-key',
23
+ * value: 'your-api-key',
24
+ * });
25
+ *
26
+ * @example
27
+ * // Dynamic value (async function)
28
+ * const middleware = apiKeyAuthMiddleware({
29
+ * header: 'x-api-key',
30
+ * value: async () => fetchApiKey(),
14
31
  * });
15
32
  */
16
33
  declare function apiKeyAuthMiddleware(config: ApiKey): Middleware;
@@ -23,8 +40,15 @@ declare function apiKeyAuthMiddleware(config: ApiKey): Middleware;
23
40
  * @returns {Middleware} The middleware for Basic authentication.
24
41
  *
25
42
  * @example
43
+ * // Static credentials
44
+ * const middleware = basicAuthMiddleware({
45
+ * credentials: { username: 'user', password: 'pass' },
46
+ * });
47
+ *
48
+ * @example
49
+ * // Dynamic credentials (async function)
26
50
  * const middleware = basicAuthMiddleware({
27
- * credentials: async () => ({ username: 'user', password: 'pass' }),
51
+ * credentials: async () => fetchCredentials(),
28
52
  * });
29
53
  */
30
54
  declare function basicAuthMiddleware(config: Basic): Middleware;
@@ -37,14 +61,22 @@ declare function basicAuthMiddleware(config: Basic): Middleware;
37
61
  * @returns {Middleware} The middleware for OAuth 1.0a authentication.
38
62
  *
39
63
  * @example
64
+ * // Static credentials
40
65
  * const middleware = oAuth10aAuthMiddleware({
41
66
  * algorithm: 'HMAC-SHA256',
42
- * credentials: async () => ({
67
+ * credentials: {
43
68
  * consumerKey: 'key',
44
69
  * consumerSecret: 'secret',
45
70
  * token: 'token',
46
71
  * tokenSecret: 'tokenSecret',
47
- * }),
72
+ * },
73
+ * });
74
+ *
75
+ * @example
76
+ * // Dynamic credentials (async function)
77
+ * const middleware = oAuth10aAuthMiddleware({
78
+ * algorithm: 'HMAC-SHA256',
79
+ * credentials: async () => fetchOAuthCredentials(),
48
80
  * });
49
81
  */
50
82
  declare function oAuth10aAuthMiddleware(config: OAuth10a): Middleware;
@@ -56,11 +88,20 @@ declare function oAuth10aAuthMiddleware(config: OAuth10a): Middleware;
56
88
  * @returns {Middleware} The middleware for OAuth 2.0 authentication.
57
89
  *
58
90
  * @example
91
+ * // Static token
92
+ * const middleware = oAuth20AuthMiddleware({
93
+ * token: 'your-access-token',
94
+ * tokenType: 'Bearer',
95
+ * });
96
+ *
97
+ * @example
98
+ * // Dynamic token (async function)
59
99
  * const middleware = oAuth20AuthMiddleware({
60
- * token: async () => 'your-access-token',
100
+ * token: async () => fetchAccessToken(),
61
101
  * tokenType: 'Bearer',
62
102
  * });
63
103
  */
64
104
  declare function oAuth20AuthMiddleware(options: OAuth20): Middleware;
65
- export type { ApiKey, Basic, OAuth10a, OAuth20 };
66
- export { apiKeyAuthMiddleware, basicAuthMiddleware, oAuth10aAuthMiddleware, oAuth20AuthMiddleware };
105
+ export { resolve };
106
+ export type { ApiKey, Basic, OAuth10a, OAuth20, Resolvable };
107
+ export { apiKeyAuthMiddleware, basicAuthMiddleware, oAuth10aAuthMiddleware, oAuth20AuthMiddleware, resignOauth10aRequest, };
@@ -1,10 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resignOauth10aRequest = void 0;
4
+ exports.resolve = resolve;
3
5
  exports.apiKeyAuthMiddleware = apiKeyAuthMiddleware;
4
6
  exports.basicAuthMiddleware = basicAuthMiddleware;
5
7
  exports.oAuth10aAuthMiddleware = oAuth10aAuthMiddleware;
6
8
  exports.oAuth20AuthMiddleware = oAuth20AuthMiddleware;
7
9
  const oauth10a_1 = require("./oauth10a/oauth10a");
10
+ Object.defineProperty(exports, "resignOauth10aRequest", { enumerable: true, get: function () { return oauth10a_1.resignOauth10aRequest; } });
11
+ /**
12
+ * Resolves a `Resolvable<T>` value to its underlying type.
13
+ * If the value is a function, it is called and awaited; otherwise, it is returned as-is.
14
+ *
15
+ * @param {Resolvable<T>} value - The resolvable value.
16
+ * @returns {Promise<T>} The resolved value.
17
+ */
18
+ async function resolve(value) {
19
+ return typeof value === 'function' ? await value() : value;
20
+ }
8
21
  /**
9
22
  * Creates an openapi-fetch middleware for API key authentication.
10
23
  * This middleware sets the API key in the specified header for each request.
@@ -13,15 +26,23 @@ const oauth10a_1 = require("./oauth10a/oauth10a");
13
26
  * @returns {Middleware} The middleware for API key authentication.
14
27
  *
15
28
  * @example
29
+ * // Static value
30
+ * const middleware = apiKeyAuthMiddleware({
31
+ * header: 'x-api-key',
32
+ * value: 'your-api-key',
33
+ * });
34
+ *
35
+ * @example
36
+ * // Dynamic value (async function)
16
37
  * const middleware = apiKeyAuthMiddleware({
17
38
  * header: 'x-api-key',
18
- * value: async () => 'your-api-key',
39
+ * value: async () => fetchApiKey(),
19
40
  * });
20
41
  */
21
42
  function apiKeyAuthMiddleware(config) {
22
43
  return {
23
44
  onRequest: async ({ request }) => {
24
- request.headers.set(config.header, await config.value());
45
+ request.headers.set(config.header, await resolve(config.value));
25
46
  },
26
47
  };
27
48
  }
@@ -34,14 +55,21 @@ function apiKeyAuthMiddleware(config) {
34
55
  * @returns {Middleware} The middleware for Basic authentication.
35
56
  *
36
57
  * @example
58
+ * // Static credentials
37
59
  * const middleware = basicAuthMiddleware({
38
- * credentials: async () => ({ username: 'user', password: 'pass' }),
60
+ * credentials: { username: 'user', password: 'pass' },
61
+ * });
62
+ *
63
+ * @example
64
+ * // Dynamic credentials (async function)
65
+ * const middleware = basicAuthMiddleware({
66
+ * credentials: async () => fetchCredentials(),
39
67
  * });
40
68
  */
41
69
  function basicAuthMiddleware(config) {
42
70
  return {
43
71
  onRequest: async ({ request }) => {
44
- const { username, password } = await config.credentials();
72
+ const { username, password } = await resolve(config.credentials);
45
73
  request.headers.set('Authorization', `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`);
46
74
  },
47
75
  };
@@ -55,14 +83,22 @@ function basicAuthMiddleware(config) {
55
83
  * @returns {Middleware} The middleware for OAuth 1.0a authentication.
56
84
  *
57
85
  * @example
86
+ * // Static credentials
58
87
  * const middleware = oAuth10aAuthMiddleware({
59
88
  * algorithm: 'HMAC-SHA256',
60
- * credentials: async () => ({
89
+ * credentials: {
61
90
  * consumerKey: 'key',
62
91
  * consumerSecret: 'secret',
63
92
  * token: 'token',
64
93
  * tokenSecret: 'tokenSecret',
65
- * }),
94
+ * },
95
+ * });
96
+ *
97
+ * @example
98
+ * // Dynamic credentials (async function)
99
+ * const middleware = oAuth10aAuthMiddleware({
100
+ * algorithm: 'HMAC-SHA256',
101
+ * credentials: async () => fetchOAuthCredentials(),
66
102
  * });
67
103
  */
68
104
  function oAuth10aAuthMiddleware(config) {
@@ -81,8 +117,16 @@ function oAuth10aAuthMiddleware(config) {
81
117
  * @returns {Middleware} The middleware for OAuth 2.0 authentication.
82
118
  *
83
119
  * @example
120
+ * // Static token
121
+ * const middleware = oAuth20AuthMiddleware({
122
+ * token: 'your-access-token',
123
+ * tokenType: 'Bearer',
124
+ * });
125
+ *
126
+ * @example
127
+ * // Dynamic token (async function)
84
128
  * const middleware = oAuth20AuthMiddleware({
85
- * token: async () => 'your-access-token',
129
+ * token: async () => fetchAccessToken(),
86
130
  * tokenType: 'Bearer',
87
131
  * });
88
132
  */
@@ -90,7 +134,7 @@ function oAuth20AuthMiddleware(options) {
90
134
  return {
91
135
  onRequest: async ({ request }) => {
92
136
  const { tokenType = 'Bearer' } = options;
93
- request.headers.set('Authorization', `${tokenType} ${await options.token()}`);
137
+ request.headers.set('Authorization', `${tokenType} ${await resolve(options.token)}`);
94
138
  },
95
139
  };
96
140
  }
@@ -10,3 +10,21 @@ import { OAuth10a } from '../authentications';
10
10
  * @returns {Promise<string>} The generated OAuth 1.0a Authorization header.
11
11
  */
12
12
  export declare function generateOauthParams(request: MiddlewareCallbackParams['request'], options: MiddlewareCallbackParams['options'], params: MiddlewareCallbackParams['params'], config: OAuth10a): Promise<string>;
13
+ /**
14
+ * Standalone function that re-signs a `Request` with fresh OAuth 1.0a credentials.
15
+ * This function derives all information (URL, method, query params, body)
16
+ * directly from the `Request` object, without requiring openapi-fetch middleware context.
17
+ *
18
+ * Designed for use with the retry middleware's `onRetry` hook to regenerate
19
+ * OAuth 1.0a signatures on retried requests.
20
+ *
21
+ * @param {Request} request - The request to re-sign.
22
+ * @param {OAuth10a} config - The OAuth 1.0a configuration.
23
+ * @returns {Promise<Request>} The request with a fresh `Authorization` header.
24
+ *
25
+ * @example
26
+ * client.use(retryMiddleware({
27
+ * onRetry: ({ request }) => resignOauth10aRequest(request, config),
28
+ * }));
29
+ */
30
+ export declare function resignOauth10aRequest(request: Request, config: OAuth10a): Promise<Request>;
@@ -4,8 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateOauthParams = generateOauthParams;
7
+ exports.resignOauth10aRequest = resignOauth10aRequest;
7
8
  const crypto_1 = __importDefault(require("crypto"));
8
9
  const oauth_sign_1 = require("oauth-sign");
10
+ const authentications_1 = require("../authentications");
9
11
  /**
10
12
  * Determines whether a given URL is absolute.
11
13
  *
@@ -129,25 +131,16 @@ function shouldGenerateBodyHash(body, method, includeBodyHash) {
129
131
  return includeBodyHash === true;
130
132
  }
131
133
  /**
132
- * Generates OAuth 1.0a parameters for signing a request.
134
+ * Core signing logic shared by both the middleware path and the standalone re-sign path.
133
135
  *
134
- * @param {MiddlewareCallbackParams['request']} request - The request object.
135
- * @param {MiddlewareCallbackParams['options']} options - The options object.
136
- * @param {MiddlewareCallbackParams['params']} params - The parameters object.
137
- * @param {OAuth10a} config - The OAuth 1.0a configuration.
138
- * @returns {Promise<string>} The generated OAuth 1.0a Authorization header.
136
+ * @param input - The request details needed for signing.
137
+ * @param config - The OAuth 1.0a configuration.
138
+ * @returns The OAuth 1.0a Authorization header value (without the "OAuth " prefix).
139
139
  */
140
- async function generateOauthParams(request, options, params, config) {
140
+ async function signOauth10a(input, config) {
141
141
  const { algorithm, includeBodyHash = 'auto', realm, callback, verifier } = config;
142
- const { consumerKey, consumerSecret, token, tokenSecret } = await config.credentials();
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();
142
+ const { method, oauthUrl, body, contentType, query } = input;
143
+ const { consumerKey, consumerSecret, token, tokenSecret } = await (0, authentications_1.resolve)(config.credentials);
151
144
  const oauthParams = {
152
145
  oauth_consumer_key: consumerKey,
153
146
  oauth_nonce: crypto_1.default.randomUUID(),
@@ -168,8 +161,8 @@ async function generateOauthParams(request, options, params, config) {
168
161
  }
169
162
  const paramsToSign = {};
170
163
  addParamsToSign(paramsToSign, oauthParams);
171
- if (params.query) {
172
- addParamsToSign(paramsToSign, params.query);
164
+ if (query) {
165
+ addParamsToSign(paramsToSign, query);
173
166
  }
174
167
  // If user submit a form, then include form parameters in the
175
168
  // signature as parameters rather than the body hash
@@ -184,7 +177,6 @@ async function generateOauthParams(request, options, params, config) {
184
177
  oauthParams.oauth_body_hash = bodyHash;
185
178
  addParamToSign(paramsToSign, 'oauth_body_hash', bodyHash);
186
179
  }
187
- const oauthUrl = getOAuthUrl(options.baseUrl, url);
188
180
  oauthParams.oauth_signature = (0, oauth_sign_1.sign)(algorithm, method, oauthUrl, paramsToSign, consumerSecret, tokenSecret);
189
181
  // realm should not be included in the signature calculation
190
182
  // but is optional in the OAuth 1.0 Authorization header
@@ -197,3 +189,55 @@ async function generateOauthParams(request, options, params, config) {
197
189
  .map(e => [e[0], '="', (0, oauth_sign_1.rfc3986)(e[1]), '"'].join(''))
198
190
  .join(',');
199
191
  }
192
+ /**
193
+ * Generates OAuth 1.0a parameters for signing a request.
194
+ *
195
+ * @param {MiddlewareCallbackParams['request']} request - The request object.
196
+ * @param {MiddlewareCallbackParams['options']} options - The options object.
197
+ * @param {MiddlewareCallbackParams['params']} params - The parameters object.
198
+ * @param {OAuth10a} config - The OAuth 1.0a configuration.
199
+ * @returns {Promise<string>} The generated OAuth 1.0a Authorization header.
200
+ */
201
+ async function generateOauthParams(request, options, params, config) {
202
+ const clonedRequest = request.clone();
203
+ const method = (clonedRequest.method || 'GET').toUpperCase();
204
+ const url = combineUrlAndPathParams(clonedRequest.url, params.path);
205
+ return signOauth10a({
206
+ method,
207
+ oauthUrl: getOAuthUrl(options.baseUrl, url),
208
+ contentType: clonedRequest.headers.get('Content-Type'),
209
+ body: await clonedRequest.text(),
210
+ query: params.query,
211
+ }, config);
212
+ }
213
+ /**
214
+ * Standalone function that re-signs a `Request` with fresh OAuth 1.0a credentials.
215
+ * This function derives all information (URL, method, query params, body)
216
+ * directly from the `Request` object, without requiring openapi-fetch middleware context.
217
+ *
218
+ * Designed for use with the retry middleware's `onRetry` hook to regenerate
219
+ * OAuth 1.0a signatures on retried requests.
220
+ *
221
+ * @param {Request} request - The request to re-sign.
222
+ * @param {OAuth10a} config - The OAuth 1.0a configuration.
223
+ * @returns {Promise<Request>} The request with a fresh `Authorization` header.
224
+ *
225
+ * @example
226
+ * client.use(retryMiddleware({
227
+ * onRetry: ({ request }) => resignOauth10aRequest(request, config),
228
+ * }));
229
+ */
230
+ async function resignOauth10aRequest(request, config) {
231
+ const clonedRequest = request.clone();
232
+ const method = (clonedRequest.method || 'GET').toUpperCase();
233
+ const parsedUrl = new URL(clonedRequest.url);
234
+ const oauthParams = await signOauth10a({
235
+ method,
236
+ oauthUrl: getOAuthUrl(parsedUrl.origin, parsedUrl.pathname),
237
+ contentType: clonedRequest.headers.get('Content-Type'),
238
+ body: await clonedRequest.text(),
239
+ query: parsedUrl.search ? parsedUrl.searchParams : undefined,
240
+ }, config);
241
+ request.headers.set('Authorization', `OAuth ${oauthParams}`);
242
+ return request;
243
+ }
@@ -18,14 +18,31 @@ export type { HttpRequestData, HttpResponseData } from './utils/http-response-er
18
18
  * const middleware = retryMiddleware();
19
19
  *
20
20
  * @example
21
- * // Custom configuration
21
+ * // Custom configuration with logging
22
22
  * const middleware = retryMiddleware({
23
23
  * retries: 5,
24
24
  * retryDelay: 'linear',
25
25
  * baseDelay: 200,
26
26
  * retryOn: [500, 502, 503, 504],
27
27
  * onRetry: (context) => {
28
- * console.log(`Retrying request (attempt ${context.attemptNumber})`);
28
+ * console.log(`Retrying request (attempt ${context.attempt})`);
29
+ * },
30
+ * });
31
+ *
32
+ * @example
33
+ * // Re-sign OAuth 1.0a requests on retry (returns a Request to replace the original)
34
+ * const middleware = retryMiddleware({
35
+ * retries: 3,
36
+ * onRetry: (context) => resignOauth10aRequest(context.request, oauthConfig),
37
+ * });
38
+ *
39
+ * @example
40
+ * // Combine logging and request transformation in onRetry
41
+ * const middleware = retryMiddleware({
42
+ * retries: 3,
43
+ * onRetry: async (context) => {
44
+ * console.log(`Retrying request (attempt ${context.attempt})`);
45
+ * return resignOauth10aRequest(context.request, oauthConfig);
29
46
  * },
30
47
  * });
31
48
  *
@@ -35,7 +52,7 @@ export type { HttpRequestData, HttpResponseData } from './utils/http-response-er
35
52
  * retries: 3,
36
53
  * retryCondition: async (context) => {
37
54
  * // Only retry on 503 Service Unavailable
38
- * return context.response.status === 503;
55
+ * return context.response?.status === 503;
39
56
  * },
40
57
  * });
41
58
  *
@@ -118,14 +118,31 @@ async function throwErrorIfNotOkResponse(response, request, throwOnNotOk) {
118
118
  * const middleware = retryMiddleware();
119
119
  *
120
120
  * @example
121
- * // Custom configuration
121
+ * // Custom configuration with logging
122
122
  * const middleware = retryMiddleware({
123
123
  * retries: 5,
124
124
  * retryDelay: 'linear',
125
125
  * baseDelay: 200,
126
126
  * retryOn: [500, 502, 503, 504],
127
127
  * onRetry: (context) => {
128
- * console.log(`Retrying request (attempt ${context.attemptNumber})`);
128
+ * console.log(`Retrying request (attempt ${context.attempt})`);
129
+ * },
130
+ * });
131
+ *
132
+ * @example
133
+ * // Re-sign OAuth 1.0a requests on retry (returns a Request to replace the original)
134
+ * const middleware = retryMiddleware({
135
+ * retries: 3,
136
+ * onRetry: (context) => resignOauth10aRequest(context.request, oauthConfig),
137
+ * });
138
+ *
139
+ * @example
140
+ * // Combine logging and request transformation in onRetry
141
+ * const middleware = retryMiddleware({
142
+ * retries: 3,
143
+ * onRetry: async (context) => {
144
+ * console.log(`Retrying request (attempt ${context.attempt})`);
145
+ * return resignOauth10aRequest(context.request, oauthConfig);
129
146
  * },
130
147
  * });
131
148
  *
@@ -135,7 +152,7 @@ async function throwErrorIfNotOkResponse(response, request, throwOnNotOk) {
135
152
  * retries: 3,
136
153
  * retryCondition: async (context) => {
137
154
  * // Only retry on 503 Service Unavailable
138
- * return context.response.status === 503;
155
+ * return context.response?.status === 503;
139
156
  * },
140
157
  * });
141
158
  *
@@ -204,12 +221,16 @@ async function performRetries(config, context) {
204
221
  const delay = await config.retryDelay(attempt, { ...context, attempt, response });
205
222
  await new Promise(resolve => setTimeout(resolve, delay));
206
223
  if (config.onRetry) {
207
- await config.onRetry({ ...context, attempt, response });
224
+ const mutatedReq = await config.onRetry({ ...context, attempt, response });
225
+ if (mutatedReq instanceof Request) {
226
+ context = { ...context, attempt, request: mutatedReq };
227
+ }
208
228
  }
209
229
  try {
230
+ const clonedRequest = context.request.clone();
210
231
  const request = config.shouldResetTimeout
211
- ? new Request(context.request)
212
- : new Request(context.request, { signal: context.request.signal });
232
+ ? clonedRequest
233
+ : new Request(clonedRequest, { signal: context.request.signal });
213
234
  response = await config.fetch(request);
214
235
  context = { ...context, attempt: attempt + 1, response, error: null };
215
236
  attempt++;
@@ -1,28 +1,44 @@
1
+ /**
2
+ * A value that can be provided either statically or as a function (sync or async).
3
+ * Used by authentication config interfaces to allow both static credentials
4
+ * and dynamic credential retrieval (e.g., from a secrets manager).
5
+ *
6
+ * @example
7
+ * // Static value
8
+ * const value: Resolvable<string> = 'my-api-key';
9
+ *
10
+ * // Sync function
11
+ * const value: Resolvable<string> = () => getKey();
12
+ *
13
+ * // Async function
14
+ * const value: Resolvable<string> = async () => await fetchKey();
15
+ */
16
+ export type Resolvable<T> = T | (() => T | Promise<T>);
1
17
  /**
2
18
  * Represents an API key authentication method.
3
19
  *
4
20
  * This interface is used for API key-based authentication, where the key is sent
5
- * in a specific header. The value of the API key is retrieved asynchronously.
21
+ * in a specific header. The value can be a static string or a function that returns one.
6
22
  *
7
23
  * @interface ApiKey
8
24
  * @property {string} header - The header name where the API key will be set.
9
- * @property {() => Promise<string>} value - A function that returns a promise resolving to the API key value.
25
+ * @property {Resolvable<string>} value - The API key value, or a function returning it.
10
26
  */
11
27
  export interface ApiKey {
12
28
  header: string;
13
- value: () => Promise<string>;
29
+ value: Resolvable<string>;
14
30
  }
15
31
  /**
16
32
  * Represents basic authentication credentials.
17
33
  *
18
34
  * This interface is used for basic authentication, where the username and password
19
- * are retrieved asynchronously.
35
+ * can be provided statically or retrieved dynamically via a function.
20
36
  *
21
37
  * @interface Basic
22
- * @property {() => Promise<{ username: string; password: string }>} credentials - A function that returns a promise resolving to the username and password.
38
+ * @property {Resolvable<{ username: string; password: string }>} credentials - The credentials, or a function returning them.
23
39
  */
24
40
  export interface Basic {
25
- credentials: () => Promise<{
41
+ credentials: Resolvable<{
26
42
  username: string;
27
43
  password: string;
28
44
  }>;
@@ -31,12 +47,12 @@ export interface Basic {
31
47
  * Represents OAuth 1.0a authentication credentials.
32
48
  *
33
49
  * This interface is used for OAuth 1.0a authentication, where the consumer key, consumer secret,
34
- * token, and token secret are retrieved asynchronously. It also supports optional parameters
35
- * like body hash inclusion, realm, callback, and verifier.
50
+ * token, and token secret can be provided statically or retrieved dynamically via a function.
51
+ * It also supports optional parameters like body hash inclusion, realm, callback, and verifier.
36
52
  *
37
53
  * @interface OAuth10a
38
54
  * @property {'HMAC-SHA1' | 'HMAC-SHA256'} algorithm - The signing algorithm to use.
39
- * @property {() => Promise<{ consumerKey: string; consumerSecret: string; token?: string; tokenSecret: string }>} credentials - A function that returns a promise resolving to the OAuth 1.0a credentials.
55
+ * @property {Resolvable<{ consumerKey: string; consumerSecret: string; token?: string; tokenSecret: string }>} credentials - The OAuth 1.0a credentials, or a function returning them.
40
56
  * @property {boolean | 'auto'} [includeBodyHash] - Whether to include a body hash in the signature. Defaults to 'auto'.
41
57
  * @property {string} [realm] - The realm parameter for the Authorization header.
42
58
  * @property {string} [callback] - The callback URL for OAuth 1.0a.
@@ -44,7 +60,7 @@ export interface Basic {
44
60
  */
45
61
  export interface OAuth10a {
46
62
  algorithm: 'HMAC-SHA1' | 'HMAC-SHA256';
47
- credentials: () => Promise<{
63
+ credentials: Resolvable<{
48
64
  consumerKey: string;
49
65
  consumerSecret: string;
50
66
  token?: string;
@@ -58,14 +74,15 @@ export interface OAuth10a {
58
74
  /**
59
75
  * Represents OAuth 2.0 authentication credentials.
60
76
  *
61
- * This interface is used for OAuth 2.0 authentication, where an access token is retrieved
62
- * asynchronously. It also supports an optional token type (e.g., 'Bearer').
77
+ * This interface is used for OAuth 2.0 authentication, where an access token can be
78
+ * provided statically or retrieved dynamically via a function.
79
+ * It also supports an optional token type (e.g., 'Bearer').
63
80
  *
64
81
  * @interface OAuth20
65
- * @property {() => Promise<string>} token - A function that returns a promise resolving to the access token.
82
+ * @property {Resolvable<string>} token - The access token, or a function returning it.
66
83
  * @property {string} [tokenType] - The type of the token (e.g., 'Bearer'). Defaults to 'Bearer' if not specified.
67
84
  */
68
85
  export interface OAuth20 {
69
- token: () => Promise<string>;
86
+ token: Resolvable<string>;
70
87
  tokenType?: string;
71
88
  }
@@ -35,10 +35,15 @@ export type RetryDelayFn = (attempt: number, context: RetryContext) => number |
35
35
  * Function type for the onRetry callback.
36
36
  * Called before each retry attempt.
37
37
  *
38
+ * - If the function returns a `Request` (or `Promise<Request>`), that request replaces
39
+ * the current one for the retry attempt. This is useful for regenerating authentication
40
+ * headers (e.g., OAuth 1.0a re-signing).
41
+ * - If the function returns `void`, the original request is used as-is.
42
+ *
38
43
  * @param {RetryContext} context - The retry context containing attempt information.
39
- * @returns {void | Promise<void>}
44
+ * @returns {Request | void | Promise<Request | void>}
40
45
  */
41
- export type OnRetryFn = (context: RetryContext) => void | Promise<void>;
46
+ export type OnRetryFn = (context: RetryContext) => Request | void | Promise<Request | void>;
42
47
  /**
43
48
  * Configuration for the retry middleware.
44
49
  *
@@ -61,7 +66,11 @@ export type OnRetryFn = (context: RetryContext) => void | Promise<void>;
61
66
  * @property {number} [baseDelay=100] - Base delay in milliseconds for built-in delay strategies.
62
67
  * @property {number} [maxDelay=30000] - Maximum delay in milliseconds between retry attempts.
63
68
  * @property {boolean} [shouldResetTimeout=false] - Whether to reset the timeout between retries.
64
- * @property {OnRetryFn} [onRetry] - Callback function executed before each retry attempt.
69
+ * @property {OnRetryFn} [onRetry]
70
+ * - Callback executed before each retry attempt (not the initial request).
71
+ * - If it returns a `Request`, that request replaces the current one for the retry.
72
+ * Useful for regenerating authentication headers (e.g., OAuth 1.0a re-signing).
73
+ * - If it returns `void`, the original request is used as-is.
65
74
  * @property {number[]} [retryOn]
66
75
  * - Array of HTTP status codes that should trigger a retry.
67
76
  * - Defaults to 5xx, 429, and 408 errors.
@@ -35,10 +35,13 @@ class HttpResponseError extends Error {
35
35
  */
36
36
  static async create(response, request) {
37
37
  const url = new URL(request.url);
38
- const [requestBody, responseBody] = await Promise.all([
39
- (0, body_parser_1.parseBody)(request.clone(), request.headers.get('content-type')),
40
- (0, body_parser_1.parseBody)(response.clone(), response.headers.get('content-type')),
41
- ]);
38
+ // If request.bodyUsed is true then it can't be cloned and will throw a type error
39
+ const requestBody = request.bodyUsed
40
+ ? null
41
+ : (0, body_parser_1.parseBody)(request.clone(), request.headers.get('content-type'));
42
+ const responseBody = response.bodyUsed
43
+ ? null
44
+ : (0, body_parser_1.parseBody)(response.clone(), response.headers.get('content-type'));
42
45
  const requestData = {
43
46
  method: request.method,
44
47
  url: request.url,