fix(admin): resolve tunnel owner emails via one listUsers scan

The upstream admin gateway deterministically truncates the first per-row
getUserById that immediately follows a request's auth check, yielding an
empty body the per-call retry cannot clear — so a couple of tunnel rows
showed owner_email '—' on every load regardless of retry or concurrency.
Replace the per-row getUserById fan-out with a single bounded listUsers
scan (same transient retry) that builds an id->email map and stops early
once every owner on the page is resolved. A single listUsers read does not
hit the truncation pattern, eliminating the dashes. Remove the now-unused
mapWithConcurrency helper and OWNER_EMAIL_CONCURRENCY constant.
This commit is contained in:
Gerhard Scheikl
2026-05-31 16:26:39 +02:00
parent 37f79ff1b1
commit cbd29445bb
2 changed files with 28 additions and 60 deletions
-30
View File
@@ -126,33 +126,3 @@ export async function withAdminRetry<R extends { error: MaybeError }>(
if (threw) throw lastThrown;
return lastResult as R;
}
/**
* Map over `items` running at most `concurrency` async tasks at a time, while
* preserving result order.
*
* Firing every GoTrue admin lookup at once (e.g. one `getUserById` per tunnel
* row) can self-inflict an upstream throttle: the proxy truncates the tail of a
* large concurrent burst, producing the very empty-body responses we retry on —
* and because the retries fire back into the same saturated window, a few rows
* can still fail. Bounding the concurrency keeps each lookup inside the
* throttle's allowance so {@link withAdminRetry} reliably resolves every row.
*/
export async function mapWithConcurrency<T, R>(
items: readonly T[],
concurrency: number,
task: (item: T, index: number) => Promise<R>,
): Promise<R[]> {
const results = new Array<R>(items.length);
const limit = Math.max(1, Math.min(concurrency, items.length || 1));
let next = 0;
async function worker(): Promise<void> {
for (;;) {
const i = next++;
if (i >= items.length) return;
results[i] = await task(items[i], i);
}
}
await Promise.all(Array.from({ length: limit }, () => worker()));
return results;
}