fix(account): add retry and rate-limit backoff handling for API requests
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-28 - 1.1.1 - fix(account)
|
||||
add retry and rate-limit backoff handling for API requests
|
||||
|
||||
- Enable retries and 429 backoff handling for JSON and text API requests.
|
||||
- Add exponential backoff retries for binary downloads when rate limited.
|
||||
- Introduce a small delay between paginated requests to reduce API throttling.
|
||||
|
||||
## 2026-03-28 - 1.1.0 - feat(build)
|
||||
modernize the build configuration and add project documentation
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@apiclient.xyz/bookstack',
|
||||
version: '1.1.0',
|
||||
version: '1.1.1',
|
||||
description: 'A TypeScript API client for BookStack, providing easy access to books, chapters, pages, shelves, and more.'
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ export class BookStackAccount {
|
||||
let builder = plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.header('Authorization', `Token ${this.tokenId}:${this.tokenSecret}`)
|
||||
.header('Content-Type', 'application/json');
|
||||
.header('Content-Type', 'application/json')
|
||||
.retry(3)
|
||||
.handle429Backoff({ maxRetries: 5, maxWaitTime: 30000, fallbackDelay: 2000 });
|
||||
|
||||
if (data) {
|
||||
builder = builder.json(data);
|
||||
@@ -73,6 +75,10 @@ export class BookStackAccount {
|
||||
break;
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
throw new Error(`Rate limited: ${method} ${path} — retries exhausted`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
@@ -95,7 +101,9 @@ export class BookStackAccount {
|
||||
let builder = plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.header('Authorization', `Token ${this.tokenId}:${this.tokenSecret}`)
|
||||
.header('Accept', 'text/plain');
|
||||
.header('Accept', 'text/plain')
|
||||
.retry(3)
|
||||
.handle429Backoff({ maxRetries: 5, maxWaitTime: 30000, fallbackDelay: 2000 });
|
||||
|
||||
let response: Awaited<ReturnType<typeof builder.get>>;
|
||||
switch (method) {
|
||||
@@ -113,6 +121,10 @@ export class BookStackAccount {
|
||||
break;
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
throw new Error(`Rate limited: ${method} ${path} — retries exhausted`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
@@ -124,16 +136,28 @@ export class BookStackAccount {
|
||||
/** @internal */
|
||||
async requestBinary(path: string): Promise<Uint8Array> {
|
||||
const url = `${this.baseUrl}/api${path}`;
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Token ${this.tokenId}:${this.tokenSecret}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`GET ${path}: ${response.status} ${response.statusText}`);
|
||||
const maxRetries = 3;
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Token ${this.tokenId}:${this.tokenSecret}`,
|
||||
},
|
||||
});
|
||||
if (response.status === 429) {
|
||||
if (attempt === maxRetries) {
|
||||
throw new Error(`Rate limited: GET ${path} — retries exhausted`);
|
||||
}
|
||||
const delay = 2000 * Math.pow(2, attempt);
|
||||
await new Promise((r) => setTimeout(r, delay));
|
||||
continue;
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`GET ${path}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const buf = await response.arrayBuffer();
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
const buf = await response.arrayBuffer();
|
||||
return new Uint8Array(buf);
|
||||
throw new Error(`GET ${path}: max retries exceeded`);
|
||||
}
|
||||
|
||||
/** @internal — build a URL with list query params */
|
||||
|
||||
@@ -20,6 +20,9 @@ export async function autoPaginate<T>(
|
||||
const all: T[] = [];
|
||||
let offset = 0;
|
||||
while (true) {
|
||||
if (offset > 0) {
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
const result = await fetchPage(offset, count);
|
||||
all.push(...result.data);
|
||||
offset += count;
|
||||
|
||||
Reference in New Issue
Block a user