73 lines
2.2 KiB
TypeScript
73 lines
2.2 KiB
TypeScript
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 };
|
|
}
|