diff --git a/src/client.ts b/src/client.ts index 47e6a49..52302c8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -344,7 +344,7 @@ export class ElydoraClient { body: body !== undefined ? JSON.stringify(body) : undefined, }); - if (attempt < this.maxRetries && (res.status === 429 || res.status >= 500)) { + if (attempt < this.maxRetries && isIdempotent(method) && (res.status === 429 || res.status >= 500)) { const retryAfter = res.headers.get('Retry-After'); const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1000 @@ -372,6 +372,13 @@ export class ElydoraClient { } } +const IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE']); + +/** A lost response to a non-idempotent request must not be replayed. */ +function isIdempotent(method: string): boolean { + return IDEMPOTENT_METHODS.has(method.toUpperCase()); +} + async function handleResponse(res: Response): Promise { if (res.ok) { if (res.status === 204) { diff --git a/src/types.ts b/src/types.ts index f849bc5..113cb66 100644 --- a/src/types.ts +++ b/src/types.ts @@ -384,6 +384,7 @@ export interface RotateApiTokenResponse { readonly token: string; readonly expires_at: number | null; readonly token_id: string; + readonly previous_token_grace_until: number; } export interface HealthResponse {