spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1/**2 * Typed environment for the API. Values come from process.env; in development the repo-root `.env`3 * is loaded through node's `process.loadEnvFile` (never overriding already-set variables).4 */5import { existsSync } from "node:fs";6import { dirname, join, resolve } from "node:path";7import { fileURLToPath } from "node:url";8import process from "node:process";910export interface ApiEnv {11 port: number;12 host: string;13 databaseUrl: string;14 redisUrl: string;15 clickhouseUrl: string;16 clickhouseDb: string;17 s3Endpoint: string;18 s3Bucket: string;19 s3AccessKey: string;20 s3SecretKey: string;21 s3Region: string;22 /** null when DCI_ADMIN_TOKEN is missing, empty or the "change-me" placeholder → admin API answers 503 */23 adminToken: string | null;24 /** internal HTTP server of the worker (GET /trace/<documentId>, GET /data-gaps) */25 workerUrl: string;26 /** directory holding connector YAML files */27 configDir: string;28 /** optional path to the worker connectors registry (parsers) for the dev tool; null = generic only */29 workerConnectorsDir: string | null;30 logLevel: string;31 cacheEnabled: boolean;32 publicRateLimit: number;33 adminRateLimit: number;34 trustedProxies: string[];35 siteUrl: string;36}3738export function findRepoRoot(start: string = process.cwd()): string {39 let dir = resolve(start);40 for (let i = 0; i < 8; i++) {41 if (existsSync(join(dir, "pnpm-workspace.yaml"))) return dir;42 const parent = dirname(dir);43 if (parent === dir) break;44 dir = parent;45 }46 const here = dirname(fileURLToPath(import.meta.url));47 return resolve(here, "..", "..", "..");48}4950let envFileLoaded = false;51export function loadEnvFile(file = ".env"): boolean {52 if (envFileLoaded) return true;53 const path = file.startsWith("/") ? file : join(findRepoRoot(), file);54 if (!existsSync(path)) return false;55 const loader = (process as unknown as { loadEnvFile?: (p: string) => void }).loadEnvFile;56 if (typeof loader !== "function") return false;57 loader.call(process, path);58 envFileLoaded = true;59 return true;60}6162function num(v: string | undefined, fallback: number): number {63 const n = Number(v);64 return v !== undefined && v !== "" && Number.isFinite(n) ? n : fallback;65}6667let cached: ApiEnv | null = null;68export function getEnv(): ApiEnv {69 if (cached) return cached;70 const e = process.env;71 const root = findRepoRoot();72 const workerDir = e.DCI_WORKER_CONNECTORS_DIR ?? join(root, "apps", "worker", "src", "connectors");73 cached = {74 port: num(e.API_PORT, 8311),75 host: e.API_HOST ?? "0.0.0.0",76 databaseUrl: e.DATABASE_URL ?? "postgres://dci:dci@127.0.0.1:5432/dci",77 redisUrl: e.REDIS_URL ?? "redis://127.0.0.1:6379/0",78 clickhouseUrl: e.CLICKHOUSE_URL ?? "http://127.0.0.1:8123",79 clickhouseDb: e.CLICKHOUSE_DB ?? "dci",80 s3Endpoint: e.S3_ENDPOINT ?? "http://127.0.0.1:9000",81 s3Bucket: e.S3_BUCKET ?? "dci-raw",82 s3AccessKey: e.S3_ACCESS_KEY ?? "dci",83 s3SecretKey: e.S3_SECRET_KEY ?? "",84 s3Region: e.S3_REGION ?? "us-east-1",85 adminToken: e.DCI_ADMIN_TOKEN && e.DCI_ADMIN_TOKEN.trim() !== "" && e.DCI_ADMIN_TOKEN !== "change-me" ? e.DCI_ADMIN_TOKEN : null,86 workerUrl: (e.DCI_WORKER_URL && e.DCI_WORKER_URL.trim()) || "http://127.0.0.1:8320",87 configDir: e.DCI_CONFIG_DIR ? resolve(e.DCI_CONFIG_DIR) : join(root, "config", "connectors"),88 workerConnectorsDir: existsSync(workerDir) ? workerDir : null,89 logLevel: (e.DCI_LOG_LEVEL ?? "info").toLowerCase(),90 cacheEnabled: e.DCI_API_CACHE !== "0",91 publicRateLimit: num(e.DCI_API_RATE_LIMIT, 240),92 adminRateLimit: num(e.DCI_ADMIN_RATE_LIMIT, 60),93 trustedProxies: (e.DCI_TRUSTED_PROXIES ?? "127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,::1/128").split(",").map((s) => s.trim()).filter(Boolean),94 siteUrl: e.NEXT_PUBLIC_SITE_URL ?? "https://www.datacenterindex.io",95 };96 return cached;97}9899/** Test hook: forget the cached env (after mutating process.env). */100export function resetEnv(): void {101 cached = null;102}103