spb/market-atlas
Public
1import { config, ensureDataDirs, runsApi, runsWorker } from "./config.js";2import { logger } from "./logger.js";3import { migrate } from "./db/migrate.js";4import { pool } from "./db/pool.js";5import { seed } from "./seed/index.js";6import { instruments } from "./core/instruments.js";7import { calendar } from "./core/calendar.js";8import { quoteStore } from "./core/quotes.js";9import { eventEngine } from "./core/events.js";10import { syncRegistry } from "./core/registry.js";11import { connectorManager } from "./core/connector-manager.js";12import { scheduler } from "./core/jobs.js";13import { observationWriter } from "./core/observations.js";14import { barAggregator } from "./core/bars.js";15import { rawArchive } from "./core/raw-archive.js";16import { buildServer } from "./api/server.js";17import { rowToExchange } from "./core/instruments.js";1819async function loadCalendar() {20 const [ex, hol] = await Promise.all([pool.query("select * from exchanges"), pool.query("select exchange_id, to_char(date, 'YYYY-MM-DD') as date, name, kind, close_time, source_id from exchange_holidays")]);21 calendar.load(22 ex.rows.map(rowToExchange),23 hol.rows.map((h) => ({ exchangeId: h.exchange_id, date: h.date, name: h.name, kind: h.kind, closeTime: h.close_time, sourceId: h.source_id })),24 );25}2627async function main() {28 logger.info({ env: config.appEnv, role: config.role, version: config.version }, "Market Atlas starting");29 ensureDataDirs();30 if (config.isProd && !config.adminToken) throw new Error("MA_ADMIN_TOKEN is required in production");31 await migrate();32 await seed();33 await loadCalendar();34 await instruments.load();35 await quoteStore.load();36 await eventEngine.warm();3738 if (runsWorker()) {39 await syncRegistry();40 scheduler.start();41 await connectorManager.startAll();42 }4344 let app: Awaited<ReturnType<typeof buildServer>> | null = null;45 if (runsApi()) {46 app = await buildServer();47 await app.listen({ host: config.apiHost, port: config.apiPort });48 logger.info({ host: config.apiHost, port: config.apiPort }, "API listening");49 }5051 let shuttingDown = false;52 const shutdown = async (signal: string) => {53 if (shuttingDown) return;54 shuttingDown = true;55 logger.info({ signal }, "shutting down");56 const force = setTimeout(() => process.exit(1), 20_000);57 try {58 scheduler.stop();59 await connectorManager.stopAll();60 await app?.close();61 await observationWriter.flush();62 await quoteStore.flush();63 await barAggregator.flush();64 await eventEngine.flush();65 rawArchive.flush();66 await pool.end();67 } catch (err) {68 logger.error({ err }, "shutdown error");69 } finally {70 clearTimeout(force);71 process.exit(0);72 }73 };74 process.on("SIGINT", () => void shutdown("SIGINT"));75 process.on("SIGTERM", () => void shutdown("SIGTERM"));76 process.on("unhandledRejection", (err) => logger.error({ err }, "unhandled rejection"));77 process.on("uncaughtException", (err) => {78 logger.fatal({ err }, "uncaught exception");79 void shutdown("uncaughtException");80 });81}8283main().catch((err) => {84 logger.fatal({ err }, "startup failed");85 process.exit(1);86});87