import { Config, Controller, Get, Inject } from '@midwayjs/core'; import type { Context } from '@midwayjs/koa'; import type { RpcHostReadiness, RpcHostReadinessSnapshot, } from '@cool-midway/rpc/saas-host'; @Controller('/health') export class SaasModuleHealthController { @Inject() ctx: Context; @Config('cool.rpc.moduleCode') moduleCode: string; @Config('cool.rpc.serviceVersion') serviceVersion: string; @Config('cool.rpc.minSchemaVersion') minSchemaVersion: number; @Config('cool.rpc.maxSchemaVersion') maxSchemaVersion: number; @Get('/') async health() { const readiness = await this.resolveReadiness(); if (!readiness.ready) this.ctx.status = 503; return { ...readiness, moduleCode: this.moduleCode, serviceVersion: this.serviceVersion, schemaRange: [this.minSchemaVersion, this.maxSchemaVersion], }; } private async resolveReadiness(): Promise { try { const container = this.ctx?.requestContext; if (!container) { return this.unavailable('RPC_HOST_READINESS_PROVIDER_MISSING'); } const readiness = await container.getAsync( 'rpcHostReadiness' ); if (!readiness) { return this.unavailable('RPC_HOST_READINESS_PROVIDER_MISSING'); } const snapshot = readiness?.snapshot?.(); return this.isReadinessSnapshot(snapshot) ? snapshot : this.unavailable('RPC_HOST_READINESS_SNAPSHOT_INVALID'); } catch { return this.unavailable('RPC_HOST_READINESS_UNAVAILABLE'); } } private isReadinessSnapshot( snapshot: RpcHostReadinessSnapshot | undefined ): snapshot is RpcHostReadinessSnapshot { const checkStates = ['pending', 'ok', 'error']; return Boolean( snapshot && typeof snapshot.ready === 'boolean' && ['starting', 'ready', 'degraded', 'draining', 'stopped'].includes( snapshot.status ) && snapshot.ready === (snapshot.status === 'ready') && Number.isFinite(snapshot.checkedAt) && snapshot.checks && checkStates.includes(snapshot.checks.bootstrap) && checkStates.includes(snapshot.checks.lease) && checkStates.includes(snapshot.checks.registry) ); } private unavailable(reason: string): RpcHostReadinessSnapshot { return { ready: false, status: 'degraded', reason, checkedAt: Date.now(), checks: { bootstrap: 'error', lease: 'error', registry: 'error', }, }; } }