fix(account): add retry and rate-limit backoff handling for API requests
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# 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)
|
## 2026-03-28 - 1.1.0 - feat(build)
|
||||||
modernize the build configuration and add project documentation
|
modernize the build configuration and add project documentation
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@apiclient.xyz/bookstack',
|
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.'
|
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()
|
let builder = plugins.smartrequest.SmartRequest.create()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header('Authorization', `Token ${this.tokenId}:${this.tokenSecret}`)
|
.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) {
|
if (data) {
|
||||||
builder = builder.json(data);
|
builder = builder.json(data);
|
||||||
@@ -73,6 +75,10 @@ export class BookStackAccount {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.status === 429) {
|
||||||
|
throw new Error(`Rate limited: ${method} ${path} — retries exhausted`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||||
@@ -95,7 +101,9 @@ export class BookStackAccount {
|
|||||||
let builder = plugins.smartrequest.SmartRequest.create()
|
let builder = plugins.smartrequest.SmartRequest.create()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header('Authorization', `Token ${this.tokenId}:${this.tokenSecret}`)
|
.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>>;
|
let response: Awaited<ReturnType<typeof builder.get>>;
|
||||||
switch (method) {
|
switch (method) {
|
||||||
@@ -113,6 +121,10 @@ export class BookStackAccount {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.status === 429) {
|
||||||
|
throw new Error(`Rate limited: ${method} ${path} — retries exhausted`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||||
@@ -124,16 +136,28 @@ export class BookStackAccount {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
async requestBinary(path: string): Promise<Uint8Array> {
|
async requestBinary(path: string): Promise<Uint8Array> {
|
||||||
const url = `${this.baseUrl}/api${path}`;
|
const url = `${this.baseUrl}/api${path}`;
|
||||||
const response = await fetch(url, {
|
const maxRetries = 3;
|
||||||
headers: {
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||||
Authorization: `Token ${this.tokenId}:${this.tokenSecret}`,
|
const response = await fetch(url, {
|
||||||
},
|
headers: {
|
||||||
});
|
Authorization: `Token ${this.tokenId}:${this.tokenSecret}`,
|
||||||
if (!response.ok) {
|
},
|
||||||
throw new Error(`GET ${path}: ${response.status} ${response.statusText}`);
|
});
|
||||||
|
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();
|
throw new Error(`GET ${path}: max retries exceeded`);
|
||||||
return new Uint8Array(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal — build a URL with list query params */
|
/** @internal — build a URL with list query params */
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export async function autoPaginate<T>(
|
|||||||
const all: T[] = [];
|
const all: T[] = [];
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (offset > 0) {
|
||||||
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
|
}
|
||||||
const result = await fetchPage(offset, count);
|
const result = await fetchPage(offset, count);
|
||||||
all.push(...result.data);
|
all.push(...result.data);
|
||||||
offset += count;
|
offset += count;
|
||||||
|
|||||||
Reference in New Issue
Block a user