import { NestFactory } from "@nestjs/core"; import { AppModule } from "./app.module.js"; import { env } from "./config/env.js"; import { connectKafka, disconnectKafka } from "./config/kafka.js"; import { outboxPublisher } from "./shared/outbox/outbox.publisher.js"; import { GlobalErrorFilter } from "./shared/errors/global-error.filter.js"; import { initTracer, shutdownTracer } from "./shared/observability/tracer.js"; import { logger } from "./shared/observability/logger.js"; import { registry } from "./shared/observability/metrics.js"; async function bootstrap(): Promise { initTracer(); const app = await NestFactory.create(AppModule, { bufferLogs: true }); app.useGlobalFilters(new GlobalErrorFilter()); app.enableShutdownHooks(); // Prometheus 指标端点:不鉴权,供 Prometheus 抓取。 // 返回 register.metrics()(Promise,含 Content-Type text/plain; version=0.0.4; charset=utf-8)。 app.getHttpAdapter().get("/metrics", async (req, res) => { res.set("Content-Type", registry.contentType); res.end(await registry.metrics()); }); // Connect Kafka producer/consumer before starting the outbox publisher. // Non-blocking: if Kafka is unavailable, service still starts; outbox // publisher will retry sends and messages stay pending until Kafka recovers. void connectKafka(); // Start the transactional outbox publisher - polls pending messages // and publishes them to Kafka topics defined in TOPIC_MAP. await outboxPublisher.start(); await app.listen(env.PORT); logger.info( { port: env.PORT, service: "core-edu" }, "CoreEdu service is listening", ); process.on("SIGTERM", async () => { logger.info("SIGTERM received, shutting down gracefully..."); await outboxPublisher.stop(); await disconnectKafka(); await shutdownTracer(); await app.close(); process.exit(0); }); process.on("SIGINT", async () => { logger.info("SIGINT received, shutting down gracefully..."); await outboxPublisher.stop(); await disconnectKafka(); await shutdownTracer(); await app.close(); process.exit(0); }); } void bootstrap();