snowcell
Version:
Official Snowcell Node.js/TypeScript SDK
62 lines • 2.23 kB
JavaScript
const RETRYABLE_METHODS = new Set(['HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']);
const RETRYABLE_STATUS = new Set([429, 503, 504]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export function calculateBackoff(attempt, opts) {
const backoff = (opts?.backoffFactor ?? 0.1) * 2 ** (attempt - 1);
const jitter = backoff * (opts?.jitterRatio ?? 0.1) * (Math.random() < 0.5 ? -1 : 1);
return Math.min(backoff + jitter, opts?.maxBackoffWait ?? 60);
}
export async function fetchWithRetries(input, init = {}) {
const method = (init.method ?? 'GET').toUpperCase();
const maxAttempts = init.retry?.maxAttempts ?? 10;
if (!RETRYABLE_METHODS.has(method)) {
return fetch(input, init);
}
let lastRes;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fetch(input, init);
if (!RETRYABLE_STATUS.has(res.status))
return res;
lastRes = res;
const ra = res.headers.get('retry-after');
const waitSec = ra && /^\d+$/.test(ra) ? Number(ra) : calculateBackoff(attempt, init.retry);
try {
await res.arrayBuffer(); // drain for keep-alive
}
catch { }
await sleep(waitSec * 1000);
}
if (lastRes)
return lastRes;
return fetch(input, init);
}
// SSE line parser yielding `data:` payloads
export async function* iterSSE(res) {
if (!res.body)
return;
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
let idx;
while ((idx = buffer.indexOf('\n')) >= 0) {
const line = buffer.slice(0, idx).trimEnd();
buffer = buffer.slice(idx + 1);
if (line.startsWith('data:')) {
const data = line.slice(5).trim();
if (data)
yield data;
}
}
}
if (buffer.startsWith('data:')) {
const data = buffer.slice(5).trim();
if (data)
yield data;
}
}
//# sourceMappingURL=util.js.map