spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
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 { health } from "./core/health.js";11import { syncRegistry } from "./core/registry.js";12import { connectorManager } from "./core/connector-manager.js";13import { scheduler } from "./core/jobs.js";14import { observationWriter } from "./core/observations.js";15import { barAggregator } from "./core/bars.js";16import { rawArchive } from "./core/raw-archive.js";17import { buildServer } from "./api/server.js";18import { rowToExchange } from "./core/instruments.js";1920async function loadCalendar() {21 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")]);22 calendar.load(23 ex.rows.map(rowToExchange),24 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 })),25 );26}2728async function main() {29 logger.info({ env: config.appEnv, role: config.role, version: config.version }, "Market Atlas starting");30 ensureDataDirs();31 if (config.isProd && !config.adminToken) throw new Error("MA_ADMIN_TOKEN is required in production");32 await migrate();33 await seed();34 await loadCalendar();35 await instruments.load();36 await quoteStore.load();37 await eventEngine.warm();3839 if (runsWorker()) {40 await syncRegistry();41 await health.hydrate();42 scheduler.start();43 await connectorManager.startAll();44 }4546 let app: Awaited<ReturnType<typeof buildServer>> | null = null;47 if (runsApi()) {48 app = await buildServer();49 await app.listen({ host: config.apiHost, port: config.apiPort });50 logger.info({ host: config.apiHost, port: config.apiPort }, "API listening");51 }5253 let shuttingDown = false;54 const shutdown = async (signal: string) => {55 if (shuttingDown) return;56 shuttingDown = true;57 logger.info({ signal }, "shutting down");58 const force = setTimeout(() => process.exit(1), 10_000);59 try {60 scheduler.stop();61 await connectorManager.stopAll();62 await app?.close();63 await observationWriter.flush();64 await quoteStore.flush();65 await barAggregator.flush();66 await eventEngine.flush();67 rawArchive.flush();68 await pool.end();69 } catch (err) {70 logger.error({ err }, "shutdown error");71 } finally {72 clearTimeout(force);73 process.exit(0);74 }75 };76 process.on("SIGINT", () => void shutdown("SIGINT"));77 process.on("SIGTERM", () => void shutdown("SIGTERM"));78 process.on("unhandledRejection", (err) => logger.error({ err }, "unhandled rejection"));79 process.on("uncaughtException", (err) => {80 logger.fatal({ err }, "uncaught exception");81 void shutdown("uncaughtException");82 });83}8485main().catch((err) => {86 logger.fatal({ err }, "startup failed");87 process.exit(1);88});89