Skip to content

Commit 93a0060

Browse files
committed
feat: expand client options type and add credit check with retry logic
1 parent 2dd5edb commit 93a0060

File tree

1 file changed

+35
-16
lines changed

1 file changed

+35
-16
lines changed

nodejs/src/index.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,29 @@ const defaultOption: RequestOptions = {
1313
type OptionReturnType<Opt, T> = Opt extends { unwrapData: false } ? AxiosResponse<T> : Opt extends { unwrapData: true } ? T : T
1414

1515
export type APIClientOptions = {
16-
wrapResponseErrors: boolean
16+
wrapResponseErrors: boolean;
17+
timeout?: number;
18+
retryConfig?: {
19+
maxRetries: number;
20+
baseDelay: number;
21+
};
1722
}
1823

1924
export class API {
2025
private axios: AxiosInstance
2126

22-
constructor (readonly accessToken: string, public hackmdAPIEndpointURL: string = "https://api.hackmd.io/v1", public options: APIClientOptions = { wrapResponseErrors: true }) {
27+
constructor (
28+
readonly accessToken: string,
29+
public hackmdAPIEndpointURL: string = "https://api.hackmd.io/v1",
30+
public options: APIClientOptions = {
31+
wrapResponseErrors: true,
32+
timeout: 30000,
33+
retryConfig: {
34+
maxRetries: 3,
35+
baseDelay: 100,
36+
},
37+
}
38+
) {
2339
if (!accessToken) {
2440
throw new HackMDErrors.MissingRequiredArgument('Missing access token when creating HackMD client')
2541
}
@@ -29,7 +45,7 @@ export class API {
2945
headers:{
3046
"Content-Type": "application/json",
3147
},
32-
timeout: 30000, // Increased timeout for low bandwidth
48+
timeout: options.timeout
3349
})
3450

3551
this.axios.interceptors.request.use(
@@ -77,39 +93,42 @@ export class API {
7793
}
7894
);
7995
}
80-
this.createRetryInterceptor(this.axios, 3); // Add retry interceptor with maxRetries = 3
96+
if (options.retryConfig) {
97+
this.createRetryInterceptor(this.axios, options.retryConfig.maxRetries, options.retryConfig.baseDelay);
98+
}
8199
}
82100

83-
// Utility functions for exponential backoff and retry logic
84-
private exponentialBackoff(retries: number): number {
85-
return Math.pow(2, retries) * 100; // Exponential backoff with base delay of 100ms
101+
private exponentialBackoff(retries: number, baseDelay: number): number {
102+
return Math.pow(2, retries) * baseDelay;
86103
}
87104

88105
private isRetryableError(error: AxiosError): boolean {
89-
// Retry on network errors, 5xx errors, and rate limiting (429)
90106
return (
91107
!error.response ||
92108
(error.response.status >= 500 && error.response.status < 600) ||
93109
error.response.status === 429
94110
);
95111
}
96112

97-
// Create retry interceptor function
98-
private createRetryInterceptor(axiosInstance: AxiosInstance, maxRetries: number): void {
113+
private createRetryInterceptor(axiosInstance: AxiosInstance, maxRetries: number, baseDelay: number): void {
99114
let retryCount = 0;
100115

101116
axiosInstance.interceptors.response.use(
102117
response => response,
103118
async error => {
104119
if (retryCount < maxRetries && this.isRetryableError(error)) {
105-
retryCount++;
106-
const delay = this.exponentialBackoff(retryCount);
107-
console.warn(`Retrying request... attempt #${retryCount} after delay of ${delay}ms`);
108-
await new Promise(resolve => setTimeout(resolve, delay));
109-
return axiosInstance(error.config);
120+
const remainingCredits = parseInt(error.response?.headers['x-ratelimit-userremaining'], 10);
121+
122+
if (isNaN(remainingCredits) || remainingCredits > 0) {
123+
retryCount++;
124+
const delay = this.exponentialBackoff(retryCount, baseDelay);
125+
console.warn(`Retrying request... attempt #${retryCount} after delay of ${delay}ms`);
126+
await new Promise(resolve => setTimeout(resolve, delay));
127+
return axiosInstance(error.config);
128+
}
110129
}
111130

112-
retryCount = 0; // Reset retry count after a successful request
131+
retryCount = 0; // Reset retry count after a successful request or when not retrying
113132
return Promise.reject(error);
114133
}
115134
);

0 commit comments

Comments
 (0)