This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { ApiError } from '$lib/api/client';
|
||||
import type { EventDTO } from '$lib/api/schema-helpers';
|
||||
import { getResults } from '$lib/api/tournaments';
|
||||
import type { EventResult } from './aggregate';
|
||||
|
||||
/**
|
||||
* Fetches scored results one event at a time.
|
||||
*
|
||||
* `POST /api/Tournament/GetResults` throws a 500 when any bracket in the request
|
||||
* is missing a rank-1 or rank-2 row, so asking for thirty events in one call means
|
||||
* one broken import hides all thirty. Per-event requests cost more round trips but
|
||||
* degrade to "we skipped event #97" instead of "statistics are unavailable".
|
||||
*/
|
||||
|
||||
export interface FailedEvent {
|
||||
event: EventDTO;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface LoadOutcome {
|
||||
loaded: EventResult[];
|
||||
failed: FailedEvent[];
|
||||
}
|
||||
|
||||
export interface LoadOptions {
|
||||
/** Called after each event settles, for a progress indicator. */
|
||||
onProgress?: (done: number, total: number) => void;
|
||||
/** Abort further requests, e.g. when the user changes scope mid-load. */
|
||||
signal?: AbortSignal;
|
||||
/** Parallel requests. Enough to be quick, few enough to be polite to the API. */
|
||||
concurrency?: number;
|
||||
}
|
||||
|
||||
export async function loadEventResults(
|
||||
events: EventDTO[],
|
||||
options: LoadOptions = {}
|
||||
): Promise<LoadOutcome> {
|
||||
const { onProgress, signal, concurrency = 6 } = options;
|
||||
|
||||
const targets = events.filter((event) => event.id !== undefined);
|
||||
const loaded: EventResult[] = [];
|
||||
const failed: FailedEvent[] = [];
|
||||
let done = 0;
|
||||
let next = 0;
|
||||
|
||||
async function worker() {
|
||||
while (next < targets.length) {
|
||||
if (signal?.aborted) return;
|
||||
const event = targets[next++];
|
||||
|
||||
try {
|
||||
const result = await getResults([event.id as number], { signal });
|
||||
loaded.push({ event, result });
|
||||
} catch (cause) {
|
||||
if (cause instanceof DOMException && cause.name === 'AbortError') throw cause;
|
||||
// A 401 has to stop everything: retrying N times just burns requests.
|
||||
if (cause instanceof ApiError && cause.status === 401) throw cause;
|
||||
failed.push({
|
||||
event,
|
||||
message: cause instanceof ApiError ? cause.message : 'Request failed'
|
||||
});
|
||||
}
|
||||
|
||||
onProgress?.(++done, targets.length);
|
||||
}
|
||||
}
|
||||
|
||||
const workers = Array.from({ length: Math.min(concurrency, targets.length) }, worker);
|
||||
await Promise.all(workers);
|
||||
|
||||
return { loaded, failed };
|
||||
}
|
||||
Reference in New Issue
Block a user