import { defineService, loadService } from '@hile/core'
import { Application } from '@hile/micro'
import { Http } from '@hile/http'
import { createConfigAggregator, createRuntimeReloader } from '@hile/reloader'
import appService from './micro.app.boot'
import httpService from './http.boot'
type RuntimeConfig = {
mysql: { host: string; port: number }
redis: { host: string; port: number }
flags: Record<string, boolean>
}
export default defineService('runtime.reload', async (shutdown) => {
const app = await loadService<Application>(appService)
const http = await loadService<Http>(httpService)
const reloader = createRuntimeReloader<RuntimeConfig, AppRuntime>({
debounceMs: 100,
normalize: config => ({
mysql: config.mysql,
redis: config.redis,
flags: config.flags,
}),
create: config => createAppRuntime(config),
dispose: runtime => runtime.close(),
onError: (error, context) => {
logger.error({ error, stage: context.stage }, 'runtime reload failed')
},
})
const configs = createConfigAggregator<RuntimeConfig>({
required: ['mysql', 'redis'],
defaults: { flags: {} },
debounceMs: 100,
onError: (error) => {
logger.error({ error }, 'runtime config emit failed')
},
})
configs.onChange(config => reloader.update(config))
const cleanupMysql = await app.subscribe('config:mysql', value => configs.set('mysql', value))
const cleanupRedis = await app.subscribe('config:redis', value => configs.set('redis', value))
const cleanupFlags = await app.subscribe('config:flags', value => configs.set('flags', value))
http.use(async (ctx, next) => {
const runtime = reloader.current()
if (!runtime) {
ctx.status = 503
ctx.body = { error: 'runtime is not ready' }
return
}
await runtime.handle(ctx, next)
})
shutdown(async () => {
await cleanupMysql()
await cleanupRedis()
await cleanupFlags()
configs.dispose()
await reloader.stop()
})
return reloader
})