fix(admin): eliminate GoTrue empty-body 500s under bulk load (retry-all + undici keep-alive + sequential bulk), CSV formula-injection guard

This commit is contained in:
Gerhard Scheikl
2026-05-31 17:30:04 +02:00
parent cbd29445bb
commit 8e8df7ae64
12 changed files with 134 additions and 28 deletions
+16 -6
View File
@@ -15,11 +15,20 @@
* surface as proper 4xx/5xx responses upstream.
*/
// Up to 3 attempts total (1 initial + 2 retries). Delays are applied BEFORE the
// 2nd and 3rd attempts respectively, so worst-case added latency is ~350ms —
// kept well under a second to keep the admin surface snappy.
const MAX_ATTEMPTS = 3;
const RETRY_DELAYS_MS = [100, 250];
// Up to 5 attempts total (1 initial + 4 retries). Delays are applied BEFORE
// attempts 2..5 respectively and are jittered (see `jitter`), so worst-case
// added latency is ~80+150+300+600 ≈ 1.13s plus jitter — kept under ~1.5s to
// keep the admin surface snappy while reliably riding out the empty-body /
// poisoned-keep-alive window that Node 24's bundled undici amplifies.
const MAX_ATTEMPTS = 5;
const RETRY_DELAYS_MS = [80, 150, 300, 600];
/** Apply ±30% random jitter so concurrent retries don't synchronise. */
function jitter(ms: number): number {
if (ms <= 0) return 0;
const delta = ms * 0.3;
return Math.round(ms - delta + Math.random() * 2 * delta);
}
/** Loose shape that both `AuthError` and `PostgrestError` satisfy. */
type MaybeError =
@@ -116,7 +125,8 @@ export async function withAdminRetry<R extends { error: MaybeError }>(
}
if (attempt < attempts) {
const delay = delays[attempt - 1] ?? delays[delays.length - 1] ?? 0;
const base = delays[attempt - 1] ?? delays[delays.length - 1] ?? 0;
const delay = jitter(base);
if (delay > 0) await sleep(delay);
}
}