SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
3.2 KB · 89 lines typescript
Raw Blame History
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