SPB Git

spb/market-atlas

Public
3.1 KB · 87 lines typescript
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
Files · 51f65b781db05eaf56fb220140642700827e9034
Menu

Theme