Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+3 -1
View File
@@ -4,4 +4,6 @@ import { tuneConnection } from '../db/connection.js';
const handle = new Database(workerData.dbPath, { readonly: true, fileMustExist: true });
tuneConnection(handle, true);
export function db() { return handle; }
export function db() {
return handle;
}
+79 -24
View File
@@ -3,43 +3,78 @@ import { resolve } from 'node:path';
import { config } from '../config.js';
import { db } from '../db/index.js';
import type { MetricsQuery, MetricsData } from '../../metrics.js';
import type { LeaderboardOptions, LeaderboardResponse, PlayerProfile, LeaderboardEntry } from '../../leaderboard.js';
import type {
LeaderboardOptions,
LeaderboardResponse,
PlayerProfile,
LeaderboardEntry
} from '../../leaderboard.js';
import type { CapacityHistory, CapacityQuery } from '../../capacity.js';
import { ensureHostSchema } from '../hosts/schema.js';
class ReportWorker {
private worker: Worker | null = null;
private sequence = 0;
private pending = new Map<number, { resolve: (value: any) => void; reject: (error: Error) => void; timer: NodeJS.Timeout }>();
private pending = new Map<
number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Pending requests hold resolvers for different report result types.
{ resolve: (value: any) => void; reject: (error: Error) => void; timer: NodeJS.Timeout }
>();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Each request key identifies a caller-specific report result type.
private flights = new Map<string, Promise<any>>();
async close() {
const worker = this.worker; this.worker = null;
for (const pending of this.pending.values()) { clearTimeout(pending.timer); pending.reject(new Error('Report service stopped')); }
this.pending.clear(); this.flights.clear();
const worker = this.worker;
this.worker = null;
for (const pending of this.pending.values()) {
clearTimeout(pending.timer);
pending.reject(new Error('Report service stopped'));
}
this.pending.clear();
this.flights.clear();
await worker?.terminate();
}
run<T>(kind: string, input: unknown): Promise<T> {
const requestInput = input as { query?: unknown; now?: number };
const timed = kind === 'metrics' || kind === 'capacity';
const key = JSON.stringify(timed ? [kind, requestInput.query, Math.floor(requestInput.now! / 30_000)] : [kind, input]);
const key = JSON.stringify(
timed
? [kind, requestInput.query, Math.floor(requestInput.now! / 30_000)]
: [kind, input]
);
const flight = this.flights.get(key);
if (flight) return flight;
if (this.pending.size >= 64) return Promise.reject(new Error('Reports are busy. Please retry shortly.'));
if (this.pending.size >= 64)
return Promise.reject(new Error('Reports are busy. Please retry shortly.'));
db(); // Finish migrations before opening the independent reader.
if (!this.worker) {
const worker = this.worker = new Worker(resolve(process.env.NODE_ENV === 'production' ? 'build/reports/worker.mjs' : '.svelte-kit/reports/worker.mjs'), { workerData: { dbPath: resolve(config.dbPath) } });
const worker = (this.worker = new Worker(
resolve(
process.env.NODE_ENV === 'production'
? 'build/reports/worker.mjs'
: '.svelte-kit/reports/worker.mjs'
),
{ workerData: { dbPath: resolve(config.dbPath) } }
));
worker.on('message', ({ id, value, error, elapsedMs, cached }) => {
const pending = this.pending.get(id); if (!pending) return;
clearTimeout(pending.timer); this.pending.delete(id);
if (process.env.EC_PERF_LOG === 'true') console.info('[report]', { kind, elapsedMs, cached });
if (error) pending.reject(new Error(error)); else pending.resolve(value);
const pending = this.pending.get(id);
if (!pending) return;
clearTimeout(pending.timer);
this.pending.delete(id);
if (process.env.EC_PERF_LOG === 'true')
console.info('[report]', { kind, elapsedMs, cached });
if (error) pending.reject(new Error(error));
else pending.resolve(value);
if (!this.pending.size) worker.unref();
});
const fail = (error: Error) => {
if (this.worker !== worker) return;
this.worker = null;
for (const pending of this.pending.values()) { clearTimeout(pending.timer); pending.reject(error); }
this.pending.clear(); void worker.terminate();
for (const pending of this.pending.values()) {
clearTimeout(pending.timer);
pending.reject(error);
}
this.pending.clear();
void worker.terminate();
};
worker.on('error', fail);
worker.on('exit', () => fail(new Error('Report worker stopped. Please retry.')));
@@ -49,12 +84,20 @@ class ReportWorker {
const promise = new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => {
// Reset the worker rather than leaving an expensive abandoned query queued.
const worker = this.worker; this.worker = null;
for (const item of this.pending.values()) { clearTimeout(item.timer); item.reject(new Error('Report timed out. Please retry.')); }
this.pending.clear(); void worker?.terminate();
const worker = this.worker;
this.worker = null;
for (const item of this.pending.values()) {
clearTimeout(item.timer);
item.reject(new Error('Report timed out. Please retry.'));
}
this.pending.clear();
void worker?.terminate();
}, 60_000);
this.pending.set(id, { resolve, reject, timer }); this.worker!.postMessage({ id, kind, input });
}).finally(() => { if (this.flights.get(key) === promise) this.flights.delete(key); });
this.pending.set(id, { resolve, reject, timer });
this.worker!.postMessage({ id, kind, input });
}).finally(() => {
if (this.flights.get(key) === promise) this.flights.delete(key);
});
this.flights.set(key, promise);
return promise;
}
@@ -62,12 +105,24 @@ class ReportWorker {
const metrics = new ReportWorker();
const leaderboard = new ReportWorker();
const capacity = new ReportWorker();
export const metricsReport = (query: MetricsQuery) => metrics.run<MetricsData>('metrics', { query, now: Date.now() });
export const leaderboardReport = (query: LeaderboardOptions) => leaderboard.run<LeaderboardResponse>('leaderboard', query);
export const playerReport = (guid: string, offset: number, options: LeaderboardOptions) => leaderboard.run<{ profile: PlayerProfile | null; entry: LeaderboardEntry | null }>('player', { guid, offset, options });
export const metricsReport = (query: MetricsQuery) =>
metrics.run<MetricsData>('metrics', { query, now: Date.now() });
export const leaderboardReport = (query: LeaderboardOptions) =>
leaderboard.run<LeaderboardResponse>('leaderboard', query);
export const playerReport = (guid: string, offset: number, options: LeaderboardOptions) =>
leaderboard.run<{ profile: PlayerProfile | null; entry: LeaderboardEntry | null }>('player', {
guid,
offset,
options
});
export const capacityHistoryReport = (query: CapacityQuery) => {
ensureHostSchema(db());
// Game changes current placement estimates, not measured host resources.
return capacity.run<CapacityHistory>('capacity', { query: { ...query, game: 'all' }, now: Date.now() });
return capacity.run<CapacityHistory>('capacity', {
query: { ...query, game: 'all' },
now: Date.now()
});
};
export async function closeReports() { await Promise.all([metrics.close(), leaderboard.close(), capacity.close()]); }
export async function closeReports() {
await Promise.all([metrics.close(), leaderboard.close(), capacity.close()]);
}
+29 -13
View File
@@ -3,23 +3,39 @@ import type Database from 'better-sqlite3';
export function installReportRevisions(handle: Database.Database) {
handle.exec(`CREATE TABLE IF NOT EXISTS report_revisions (topic TEXT PRIMARY KEY, revision INTEGER NOT NULL DEFAULT 0);
INSERT OR IGNORE INTO report_revisions(topic) VALUES ('metrics'), ('leaderboard');`);
for (const table of ['metric_samples', 'servers']) for (const action of ['INSERT', 'UPDATE', 'DELETE']) {
const condition = table === 'servers' && action === 'UPDATE' ? 'WHEN OLD.destroyed_at IS NOT NEW.destroyed_at OR OLD.name IS NOT NEW.name' : '';
handle.exec(`CREATE TRIGGER IF NOT EXISTS report_${table}_${action} AFTER ${action} ON ${table} ${condition}
for (const table of ['metric_samples', 'servers'])
for (const action of ['INSERT', 'UPDATE', 'DELETE']) {
const condition =
table === 'servers' && action === 'UPDATE'
? 'WHEN OLD.destroyed_at IS NOT NEW.destroyed_at OR OLD.name IS NOT NEW.name'
: '';
handle.exec(`CREATE TRIGGER IF NOT EXISTS report_${table}_${action} AFTER ${action} ON ${table} ${condition}
BEGIN UPDATE report_revisions SET revision=revision+1 WHERE topic='metrics'; END;`);
}
if (handle.prepare("SELECT 1 FROM settings WHERE key='completed_report_revisions_v1'").get()) return;
}
if (handle.prepare("SELECT 1 FROM settings WHERE key='completed_report_revisions_v1'").get())
return;
handle.transaction(() => {
for (const table of ['leaderboard_matches', 'leaderboard_match_players', 'leaderboard_players']) {
for (const table of [
'leaderboard_matches',
'leaderboard_match_players',
'leaderboard_players'
]) {
for (const action of ['INSERT', 'UPDATE', 'DELETE']) {
const name = `report_${table}_${action}`;
const versions = action === 'INSERT' ? ['NEW'] : action === 'DELETE' ? ['OLD'] : ['OLD', 'NEW'];
const condition = table === 'leaderboard_matches'
? `WHEN ${versions.map(row => `${row}.status='completed'`).join(' OR ')}`
: table === 'leaderboard_match_players'
? `WHEN ${versions.map(row => `(SELECT status FROM leaderboard_matches WHERE id=${row}.match_id)='completed'`).join(' OR ')}`
: action === 'UPDATE' ? 'WHEN OLD.name IS NOT NEW.name' : '';
const event = table === 'leaderboard_players' && action === 'UPDATE' ? 'UPDATE OF name' : action;
const versions =
action === 'INSERT' ? ['NEW'] : action === 'DELETE' ? ['OLD'] : ['OLD', 'NEW'];
const condition =
table === 'leaderboard_matches'
? `WHEN ${versions.map((row) => `${row}.status='completed'`).join(' OR ')}`
: table === 'leaderboard_match_players'
? `WHEN ${versions.map((row) => `(SELECT status FROM leaderboard_matches WHERE id=${row}.match_id)='completed'`).join(' OR ')}`
: action === 'UPDATE'
? 'WHEN OLD.name IS NOT NEW.name'
: '';
const event =
table === 'leaderboard_players' && action === 'UPDATE'
? 'UPDATE OF name'
: action;
handle.exec(`DROP TRIGGER IF EXISTS ${name};
CREATE TRIGGER ${name} AFTER ${event} ON ${table} ${condition}
BEGIN UPDATE report_revisions SET revision=revision+1 WHERE topic='leaderboard'; END;`);
+41 -11
View File
@@ -15,26 +15,56 @@ parentPort!.on('message', ({ id, kind, input }) => {
// Capacity refreshes every 30 seconds without adding write triggers
// to the heartbeat path. It also has its own worker, so large
// history ranges cannot queue ahead of fleet or player reports.
const revision = kind === 'capacity' ? 0
: (db().prepare('SELECT revision FROM report_revisions WHERE topic=?').get(topic) as { revision: number }).revision;
const revision =
kind === 'capacity'
? 0
: (
db()
.prepare('SELECT revision FROM report_revisions WHERE topic=?')
.get(topic) as { revision: number }
).revision;
const timed = kind === 'metrics' || kind === 'capacity';
const now = timed ? input.now : Date.now();
const key = JSON.stringify([kind, revision, timed ? Math.floor(now / 30_000) : 0, timed ? input.query : input]);
const key = JSON.stringify([
kind,
revision,
timed ? Math.floor(now / 30_000) : 0,
timed ? input.query : input
]);
const saved = cache.get(key);
if (saved) { cache.delete(key); cache.set(key, saved); return { value: saved.value, cached: true }; }
const value = kind === 'metrics' ? readMetrics(input.query, now)
: kind === 'capacity' ? readCapacityHistory(input.query, now)
: kind === 'leaderboard' ? getLeaderboard(input)
: { profile: getPlayerProfile(input.guid, input.offset, input.options), entry: getPlayerLeaderboardEntry(input.guid, input.options) };
if (saved) {
cache.delete(key);
cache.set(key, saved);
return { value: saved.value, cached: true };
}
const value =
kind === 'metrics'
? readMetrics(input.query, now)
: kind === 'capacity'
? readCapacityHistory(input.query, now)
: kind === 'leaderboard'
? getLeaderboard(input)
: {
profile: getPlayerProfile(input.guid, input.offset, input.options),
entry: getPlayerLeaderboardEntry(input.guid, input.options)
};
const size = Buffer.byteLength(JSON.stringify(value));
if (size < 16 * 1024 * 1024) {
cache.set(key, { value, bytes: size }); bytes += size;
cache.set(key, { value, bytes: size });
bytes += size;
while (cache.size > 64 || bytes > 32 * 1024 * 1024) {
const oldest = cache.keys().next().value!; bytes -= cache.get(oldest)!.bytes; cache.delete(oldest);
const oldest = cache.keys().next().value!;
bytes -= cache.get(oldest)!.bytes;
cache.delete(oldest);
}
}
return { value, cached: false };
})();
parentPort!.postMessage({ id, ...result, elapsedMs: performance.now() - started });
} catch (cause) { parentPort!.postMessage({ id, error: cause instanceof Error ? cause.message : 'Report failed' }); }
} catch (cause) {
parentPort!.postMessage({
id,
error: cause instanceof Error ? cause.message : 'Report failed'
});
}
});