spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import { readFileSync } from "node:fs";2import { dirname, join } from "node:path";3import { fileURLToPath } from "node:url";4import { pool } from "../db/pool.js";5import { logger } from "../logger.js";67function seedDir(): string {8 // Works from src (tsx) and from dist (bundled): seeds are copied next to dist by the build.9 const here = dirname(fileURLToPath(import.meta.url));10 for (const c of [here, join(here, "seed"), join(here, "../seed"), join(here, "../src/seed")]) {11 try {12 readFileSync(join(c, "countries.json"));13 return c;14 } catch {15 /* try next */16 }17 }18 throw new Error("seed directory not found");19}2021/** Idempotent reference data: countries, exchanges, holidays. Instruments are seeded by connectors. */22export async function seed(): Promise<void> {23 const dir = seedDir();24 const countries = JSON.parse(readFileSync(join(dir, "countries.json"), "utf8")) as Array<Record<string, unknown>>;25 const exchanges = JSON.parse(readFileSync(join(dir, "exchanges.json"), "utf8")) as Array<Record<string, any>>;26 const holidayGroups = JSON.parse(readFileSync(join(dir, "holidays.json"), "utf8")) as Array<{ exchanges: string[]; holidays: Array<Record<string, any>> }>;2728 for (const c of countries) {29 await pool.query(30 `insert into countries (code, name, region, currency) values ($1,$2,$3,$4) on conflict (code) do update set name = excluded.name, region = excluded.region, currency = excluded.currency`,31 [c.code, c.name, c.region, c.currency],32 );33 }34 for (const e of exchanges) {35 await pool.query(36 `insert into exchanges (id, mic, name, operator, country, city, timezone, currency, website, sessions, lat, lon, asset_classes)37 values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)38 on conflict (id) do update set mic = excluded.mic, name = excluded.name, operator = excluded.operator, country = excluded.country, city = excluded.city, timezone = excluded.timezone,39 currency = excluded.currency, website = excluded.website, sessions = excluded.sessions, lat = excluded.lat, lon = excluded.lon, asset_classes = excluded.asset_classes, updated_at = now()`,40 [e.id, e.mic, e.name, e.operator, e.country, e.city, e.timezone, e.currency, e.website, JSON.stringify(e.sessions), e.lat, e.lon, e.asset_classes],41 );42 }43 let holidays = 0;44 for (const g of holidayGroups) {45 for (const ex of g.exchanges) {46 for (const h of g.holidays) {47 await pool.query(48 `insert into exchange_holidays (exchange_id, date, name, kind, close_time, source_id) values ($1,$2,$3,$4,$5,'market-atlas-seed')49 on conflict (exchange_id, date) do update set name = excluded.name, kind = excluded.kind, close_time = excluded.close_time where exchange_holidays.source_id = 'market-atlas-seed'`,50 [ex, h.date, h.name, h.kind ?? "CLOSED", h.closeTime ?? null],51 );52 holidays++;53 }54 }55 }56 await pool.query(57 `insert into sources (id, name, organization, source_type, homepage, jurisdiction, rights_status, realtime_status, family, category, enabled)58 values ('market-atlas-calendar','Market Atlas market-hours engine','Market Atlas','OFFICIAL_API',null,null,'INTERNAL_ONLY','REALTIME',null,'INTERNAL',true) on conflict (id) do nothing`,59 );60 logger.info({ countries: countries.length, exchanges: exchanges.length, holidays }, "seed applied");61}62