import { UptimeRunnerApiClient } from './api-client.ts'; import { CheckExecutor } from './check-executor.ts'; import type { IRunOnceResult, IUptimeCheckResult, IUptimeRunnerConfig, TCheckJob, } from './interfaces.ts'; import denoConfig from '../deno.json' with { type: 'json' }; export class UptimeRunner { private readonly apiClient: UptimeRunnerApiClient; private readonly checkExecutor: CheckExecutor; private running = false; constructor(private readonly config: IUptimeRunnerConfig, apiClientArg?: UptimeRunnerApiClient) { this.apiClient = apiClientArg ?? new UptimeRunnerApiClient(config); this.checkExecutor = new CheckExecutor(config.runnerId); } public async run(): Promise { this.running = true; console.log(`uptimerunner ${this.config.runnerId} connected to ${this.config.instanceUrl}`); while (this.running) { const started = Date.now(); try { await this.heartbeat().catch((error) => { console.warn( `heartbeat failed: ${error instanceof Error ? error.message : String(error)}`, ); }); const result = await this.runOnce(); if (result.results.length > 0) { console.log(`reported ${result.results.length} check result(s)`); } } catch (error) { console.error( `runner iteration failed: ${error instanceof Error ? error.message : String(error)}`, ); } const elapsed = Date.now() - started; const delayMs = Math.max((this.config.pollIntervalMs ?? 30000) - elapsed, 1000); await delayFor(delayMs); } } public stop(): void { this.running = false; } public async runOnce(): Promise { const checks = await this.apiClient.fetchAssignedChecks( this.config.runnerId, this.config.labels ?? [], ); const results = await this.executeChecks(checks); await this.apiClient.submitResults(this.config.runnerId, results); return { checks, results }; } private async heartbeat(): Promise { await this.apiClient.heartbeat({ runnerId: this.config.runnerId, labels: this.config.labels, version: denoConfig.version, }); } private async executeChecks(checksArg: TCheckJob[]): Promise { const maxConcurrentChecks = this.config.maxConcurrentChecks ?? 8; const results: IUptimeCheckResult[] = []; let nextIndex = 0; const worker = async () => { while (nextIndex < checksArg.length) { const currentIndex = nextIndex++; results[currentIndex] = await this.checkExecutor.execute(checksArg[currentIndex]); } }; await Promise.all( Array.from({ length: Math.min(maxConcurrentChecks, checksArg.length) }, () => worker()), ); return results; } } async function delayFor(millisecondsArg: number): Promise { await new Promise((resolve) => setTimeout(resolve, millisecondsArg)); }