SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
3 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
3.9 KB · 103 lines typescript
Raw Blame History
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