import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { pool } from "../db/pool.js"; import { logger } from "../logger.js"; function seedDir(): string { // Works from src (tsx) and from dist (bundled): seeds are copied next to dist by the build. const here = dirname(fileURLToPath(import.meta.url)); for (const c of [here, join(here, "seed"), join(here, "../seed"), join(here, "../src/seed")]) { try { readFileSync(join(c, "countries.json")); return c; } catch { /* try next */ } } throw new Error("seed directory not found"); } /** Idempotent reference data: countries, exchanges, holidays. Instruments are seeded by connectors. */ export async function seed(): Promise { const dir = seedDir(); const countries = JSON.parse(readFileSync(join(dir, "countries.json"), "utf8")) as Array>; const exchanges = JSON.parse(readFileSync(join(dir, "exchanges.json"), "utf8")) as Array>; const holidayGroups = JSON.parse(readFileSync(join(dir, "holidays.json"), "utf8")) as Array<{ exchanges: string[]; holidays: Array> }>; for (const c of countries) { await pool.query( `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`, [c.code, c.name, c.region, c.currency], ); } for (const e of exchanges) { await pool.query( `insert into exchanges (id, mic, name, operator, country, city, timezone, currency, website, sessions, lat, lon, asset_classes) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) on conflict (id) do update set mic = excluded.mic, name = excluded.name, operator = excluded.operator, country = excluded.country, city = excluded.city, timezone = excluded.timezone, currency = excluded.currency, website = excluded.website, sessions = excluded.sessions, lat = excluded.lat, lon = excluded.lon, asset_classes = excluded.asset_classes, updated_at = now()`, [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], ); } let holidays = 0; for (const g of holidayGroups) { for (const ex of g.exchanges) { for (const h of g.holidays) { await pool.query( `insert into exchange_holidays (exchange_id, date, name, kind, close_time, source_id) values ($1,$2,$3,$4,$5,'market-atlas-seed') 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'`, [ex, h.date, h.name, h.kind ?? "CLOSED", h.closeTime ?? null], ); holidays++; } } } await pool.query( `insert into sources (id, name, organization, source_type, homepage, jurisdiction, rights_status, realtime_status, family, category, enabled) 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`, ); logger.info({ countries: countries.length, exchanges: exchanges.length, holidays }, "seed applied"); }