79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
export const DATA_SOURCE_IDS = [
|
|
'oneOsMileage',
|
|
'hydrogenDatabase',
|
|
'electricDatabase',
|
|
'etcDatabase',
|
|
] as const;
|
|
|
|
export type DataSourceId = typeof DATA_SOURCE_IDS[number];
|
|
|
|
interface Outcome {
|
|
success: boolean;
|
|
at: number;
|
|
}
|
|
|
|
const MAX_OUTCOMES = 100;
|
|
const outcomes = new Map<DataSourceId, Outcome[]>();
|
|
const startedAt = new Date().toISOString();
|
|
|
|
function lastMatching(entries: Outcome[], success: boolean): Outcome | undefined {
|
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
if (entries[index].success === success) return entries[index];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function recordDataSourceOutcome(
|
|
source: DataSourceId,
|
|
success: boolean,
|
|
at = Date.now(),
|
|
): void {
|
|
const entries = outcomes.get(source) || [];
|
|
entries.push({ success, at });
|
|
if (entries.length > MAX_OUTCOMES) entries.splice(0, entries.length - MAX_OUTCOMES);
|
|
outcomes.set(source, entries);
|
|
}
|
|
|
|
export async function observeDataSource<T>(
|
|
source: DataSourceId,
|
|
operation: () => Promise<T>,
|
|
): Promise<T> {
|
|
try {
|
|
const result = await operation();
|
|
recordDataSourceOutcome(source, true);
|
|
return result;
|
|
} catch (error) {
|
|
recordDataSourceOutcome(source, false);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function getDataSourceTelemetry(now = new Date()) {
|
|
return {
|
|
semantics: 'observed-requests' as const,
|
|
windowSize: MAX_OUTCOMES,
|
|
processStartedAt: startedAt,
|
|
checkedAt: now.toISOString(),
|
|
sources: DATA_SOURCE_IDS.map(source => {
|
|
const entries = outcomes.get(source) || [];
|
|
const failures = entries.filter(entry => !entry.success).length;
|
|
const last = entries.at(-1);
|
|
const lastSuccess = lastMatching(entries, true);
|
|
const lastFailure = lastMatching(entries, false);
|
|
return {
|
|
source,
|
|
state: !last ? 'unobserved' as const : last.success ? 'available' as const : 'failing' as const,
|
|
attempts: entries.length,
|
|
failures,
|
|
failureRate: entries.length > 0 ? Math.round(failures / entries.length * 10_000) / 10_000 : null,
|
|
lastSuccessAt: lastSuccess ? new Date(lastSuccess.at).toISOString() : null,
|
|
lastFailureAt: lastFailure ? new Date(lastFailure.at).toISOString() : null,
|
|
};
|
|
}),
|
|
};
|
|
}
|
|
|
|
export function clearDataSourceTelemetry(): void {
|
|
outcomes.clear();
|
|
}
|