import { mkdirSync } from "node:fs"; import { resolve } from "node:path"; function env(name: string, fallback?: string): string { const v = process.env[name]; if (v === undefined || v === "") { if (fallback !== undefined) return fallback; throw new Error(`missing environment variable ${name}`); } return v; } export type Role = "all" | "api" | "worker"; export const config = { appEnv: env("APP_ENV", "development"), isProd: env("APP_ENV", "development") === "production", siteUrl: env("MA_SITE_URL", "http://localhost:8390"), apiHost: env("MA_API_HOST", "127.0.0.1"), apiPort: Number(env("MA_API_PORT", "8391")), role: env("MA_ROLE", "all") as Role, databaseUrl: env("DATABASE_URL", "postgres://localhost:5432/market_atlas"), dataDir: resolve(env("MA_DATA_DIR", "./data")), adminToken: env("MA_ADMIN_TOKEN", ""), /** When set, the API process is the public edge: /v1/* served locally, everything else proxied to the Next.js web app. */ webUpstream: env("MA_WEB_UPSTREAM", ""), logJson: env("MA_LOG_JSON", "0") === "1", contactEmail: env("MA_CONTACT_EMAIL", "contact@market-atlas.co"), userAgent: `MarketAtlas/0.1 (+https://www.market-atlas.co; ${env("MA_CONTACT_EMAIL", "contact@market-atlas.co")})`, disabledConnectors: new Set( env("MA_DISABLED_CONNECTORS", "") .split(",") .map((s) => s.trim()) .filter(Boolean), ), rawSampleRate: Number(env("MA_RAW_SAMPLE_RATE", "0.02")), observationRetentionDays: Number(env("MA_OBSERVATION_RETENTION_DAYS", "45")), /** Observation writer batching. */ writerFlushMs: 1000, writerMaxBatch: 2000, // 17 columns × 2000 rows stays under Postgres' 65,535 bind-parameter limit /** Consensus: observations older than this (ms) are excluded for REALTIME sources. */ consensusFreshWindowMs: 15_000, /** * Persistence sampling for REALTIME streaming ticks: at most one stored observation per * (source, instrument, field) per interval. Consensus/events still see every tick in memory. * 0 disables sampling (store everything). */ tickPersistIntervalMs: Number(env("MA_TICK_PERSIST_INTERVAL_MS", "2000")), version: "0.1.0", }; export function ensureDataDirs(): void { for (const d of ["raw", "archive", "backups", "logs", "cache"]) mkdirSync(resolve(config.dataDir, d), { recursive: true }); } export const runsWorker = () => config.role === "all" || config.role === "worker"; export const runsApi = () => config.role === "all" || config.role === "api";