export function readConfig(env = process.env) { const rawRetentionDays = integer(env.GELIOS_RAW_RETENTION_DAYS, 14, 1, 3650); return { nodeEnv: string(env.NODE_ENV, "development"), port: integer(env.PORT, 18105, 1, 65535), databaseUrl: required(env.DATABASE_URL, "DATABASE_URL"), databasePoolSize: integer(env.GELIOS_DATABASE_POOL_SIZE, 10, 1, 50), internalAccessToken: optional(env.NODEDC_INTERNAL_ACCESS_TOKEN), tenantId: slug(required(env.GELIOS_TENANT_ID, "GELIOS_TENANT_ID"), "GELIOS_TENANT_ID"), connectionId: slug(required(env.GELIOS_CONNECTION_ID, "GELIOS_CONNECTION_ID"), "GELIOS_CONNECTION_ID"), unitScope: collectionScope(env.GELIOS_UNIT_SCOPE), allowedUnitIds: intList(env.GELIOS_ALLOWED_UNIT_IDS), intakeEnabled: boolean(env.GELIOS_INTAKE_ENABLED, false), rawRetentionDays, positionStaleAfterMs: integer(env.GELIOS_POSITION_STALE_AFTER_MS, 300_000, 1_000, 86_400_000), }; } function collectionScope(value) { const candidate = optional(value).toLowerCase() || "allowlist"; if (["allowlist", "all"].includes(candidate)) return candidate; throw new Error("GELIOS_UNIT_SCOPE_must_be_allowlist_or_all"); } export function unitIsInScope(config, sourceUnitId) { return config?.unitScope === "all" || config?.allowedUnitIds?.includes(sourceUnitId) === true; } function required(value, name) { const normalized = optional(value); if (!normalized) throw new Error(`${name}_required`); return normalized; } function optional(value) { const normalized = String(value ?? "").trim(); return normalized || ""; } function string(value, fallback) { return optional(value) || fallback; } function integer(value, fallback, min, max) { const candidate = optional(value); if (!candidate) return fallback; const number = Number.parseInt(candidate, 10); if (!Number.isInteger(number) || number < min || number > max) { throw new Error(`invalid_integer:${candidate}`); } return number; } function boolean(value, fallback) { const candidate = optional(value).toLowerCase(); if (!candidate) return fallback; if (["1", "true", "yes", "on"].includes(candidate)) return true; if (["0", "false", "no", "off"].includes(candidate)) return false; throw new Error(`invalid_boolean:${candidate}`); } function intList(value) { const values = optional(value) .split(",") .map((item) => item.trim()) .filter(Boolean) .map((item) => Number.parseInt(item, 10)); if (values.some((item) => !Number.isInteger(item) || item <= 0)) { throw new Error("GELIOS_ALLOWED_UNIT_IDS_must_contain_positive_integers"); } return [...new Set(values)].sort((left, right) => left - right); } function slug(value, name) { if (!/^[a-z0-9][a-z0-9_-]{0,95}$/i.test(value)) { throw new Error(`${name}_invalid`); } return value; }