@plantinformatics/vcf-genotype-brapi
Version:
Client and server functions to access genotype data from VCF via a custom web API and BrAPI
490 lines (439 loc) • 14.5 kB
JavaScript
/*----------------------------------------------------------------------------*/
/* Node.js globals */
/* global exports */
/* global require */
/* global process */
/*----------------------------------------------------------------------------*/
/* global Headers */
//------------------------------------------------------------------------------
/** The scope of this module is :
* . connection to a Germinate server
* . BrAPI requests are wrapped (currently)
* . provide fetchEndpoint() for other requests
*
* Related ./germinate-genotype.js
*/
//------------------------------------------------------------------------------
import BrAPI from '@solgenomics/brapijs';
// function BrAPI() { console.log('BrAPI not imported'); }
//'./build/BrAPI.js';
//------------------------------------------------------------------------------
const isNodeJs = typeof process !== 'undefined';
var bent;
//var omit;
let env;
/** based on extract from @solgenomics/brapijs/src/BrAPINode.js */
let fetch;
try {
fetch = window.fetch;
} catch(e){
fetch = require('node-fetch');
}
/*
if (isNodeJs) {
bent = require('bent');
// omit = require('lodash/object');
env = process.env;
}
else {
import('fetch').then(result => {fetch = result; console.log(fetch, fetch);});
}
*/
// import ENV from '../../config/environment'; env = ENV.germinate;
// import { omit } from 'lodash/object';
/**
* signature : (endpoint, method = 'GET', body = undefined) -> promise
* @param this Germinate
* @see fetchEndpoint()
*/
const fetchEndpointFn = isNodeJs ? fetchEndpoint_bent : fetchEndpoint_fetch;
//------------------------------------------------------------------------------
/* from https://brapi.org/get-started/3
* /serverinfo
*/
const testServerURL = 'https://test-server.brapi.org/brapi/v2';
const yambase = 'https://www.yambase.org/brapi/v1';
let
/** scheme + userinfo + host + port */
germinateServerDomain = 'https://germinate.plantinformatics.io',
germinateServerURL = germinateServerDomain + '/api';
let serverURL = germinateServerURL; // testServerURL;
const brapi_v = 'brapi/v2';
let serverURLBrAPI = germinateServerURL + '/' + brapi_v;
const germinateToken = isNodeJs && env.germinateToken;
//------------------------------------------------------------------------------
const dLog = console.debug;
const trace = 0;
/** If obj is defined and contains fieldName, copy it and replace fieldName value with value.length.
* Similar : lodash omit(obj, [fieldName]));
*/
function obscureField(obj, fieldName) {
if (obj && obj[fieldName]) {
obj = Object.assign({}, obj);
obj[fieldName] = obj[fieldName].length;
}
return obj;
}
//------------------------------------------------------------------------------
// let germinate = new Germinate(serverURL);
class Germinate {
constructor(germinateServerDomain_) {
this.init();
germinateServerDomain = germinateServerDomain_;
germinateServerURL = germinateServerDomain + '/api';
serverURL = germinateServerURL;
serverURLBrAPI = germinateServerURL + '/' + brapi_v;
// used by login() to determine isSouthGreen, for endpoint.
this.serverURL = serverURL;
}
}
export {Germinate};
Germinate.prototype.init = init;
/** If germinateToken is defined in the environment of the server,
* initialise the brapi library root BrAPINode.
*/
function init() {
const fnName = 'init';
if (germinateToken) {
this.setToken(germinateToken);
}
}
Germinate.prototype.setToken = setToken;
function setToken(token) {
this.token = token;
this.initBrAPIRoot(token);
}
Germinate.prototype.setCredentials = setCredentials;
function setCredentials(username, password) {
this.username = username;
this.password = password;
}
Germinate.prototype.connect = connect;
/** Use Germinate login 'api/token'
* @return a promise which yields undefined
*/
function connect() {
const fnName = 'connect';
console.log('Germinate', serverURL, '$germinateToken', 'this', obscureField(this, 'password'));
const
p =
this.login()
.then(token => {
console.log(fnName, token);
if (serverURL.endsWith('/spark/api')) {
// signify that login is OK, but credentials should not be sent
token = 'null';
}
token && this.setToken(token);
if (! token) { throw 'login failed'; }
});
return p;
}
Germinate.prototype.connectedP = connectedP;
/** @return a promise which resolves if/when connected and authenticated.
*/
function connectedP() {
let p;
if (! this.token) {
p = this.connect();
} else {
p = Promise.resolve();
}
return p;
}
Germinate.prototype.initBrAPIRoot = initBrAPIRoot;
function initBrAPIRoot(token) {
console.log('Germinate', serverURL, token, 'this', obscureField(this, 'password'));
this.brapi_root = BrAPI(serverURLBrAPI, "v2.0", token);
/** it is possible to change token by creating a child BrAPINode, via
* this.brapi_root.server(address, version, auth_token),
* so initBrAPIRoot() could be moved out of init().
*/
}
Germinate.prototype.serverinfo = serverinfo;
function serverinfo() {
this.brapi_root
.serverinfo()
.all((objects) => {
console.log(objects);
this.data_serverinfo = objects;
});
}
//------------------------------------------------------------------------------
Germinate.prototype.fetchEndpoint = fetchEndpoint;
/** Call bent() or fetch() for Node.js or browser respectively.
* @param this Germinate
* @param endpoint
* @param method = 'GET'
* @param body = undefined
* @return promise yielding body of API response
*/
function fetchEndpoint() {
const
fetchEndpointP = fetchEndpointFn.apply(this, arguments)
.then(response => responseValueP(response));
return fetchEndpointP;
}
/** Access the value from fetchEndpoint().
* fetch() response has .ok and .json() returns a promise.
* bent()() response is the parsed data.
*
* This is used in all cases of fetchEndpoint().
*/
function responseValueP(response) {
const fnName = 'responseValueP';
if (! response.ok || response.status !== 200) {
dLog(fnName, response.ok, response.status);
}
const
value =
! response.ok ? Promise.reject(response.status, fnName) :
response?.json ? response.json() : Promise.resolve(response);
return value;
}
//--------------------------------------
/** Use fetch() for an endpoint which is not BrAPI, i.e. is not in serverURLBrAPI.
* @param this Germinate
* @param endpoint e.g. 'marker/table'
* @return result of fetch() - promise yielding response or error
*/
function fetchEndpoint_fetch(endpoint, method = 'GET', body = undefined) {
const
fnName = 'fetchEndpoint_fetch',
tokenNull = ! this.token || (this.token === 'null'),
token = this.token || 'null',
/** for multiple connections will need to use api-server .host in place of serverURL */
isSouthGreen = serverURL.includes('gigwa.ird.fr'),
mode = isSouthGreen ? 'no-cors' : 'cors',
headerObj = {
// 'User-Agent': ...,
'Accept': '*/*', // application/json, text/plain,
// 'Accept-Language': 'en-US,en;q=0.5',
'Content-Type': 'application/json;charset=utf-8', // text/plain
/*
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-origin', // site
'Cache-Control': 'max-age=0',
*/
},
/** referrer sets the referer header. refn https://en.wikipedia.org/wiki/HTTP_referer
* https://developer.mozilla.org/en-US/docs/Web/API/fetch */
options = {
credentials : tokenNull ? 'omit' : 'include',
headers : /*new Headers(*/headerObj/*)*/,
referrer : germinateServerDomain + '/', // 'http://localhost:4200/',
method,
mode
};
if (! tokenNull) {
headerObj.Authorization = 'Bearer ' + token;
}
if (body) {
/** fetch() requires JSON.stringify(body), whereas
* bent() does not require body to be a string - it will JSON.stringify().
*/
options.body = JSON.stringify(body);
}
console.log(fnName, headerObj, obscureField(body, 'password'));
const
resultP =
fetch(serverURL + '/' + endpoint, options);
return resultP;
}
/** Mostly the same as fetchEndpoint_fetch(); the differences have been absorbed
* into fetchEndpoint_fetch() with the condition ! this.token, and
* fetchEndpoint_fetch() is used by .login() instead of this function.
*/
function fetchEndpoint_fetch_login(endpoint, method = 'GET', body = undefined) {
const
fnName = 'fetchEndpoint_fetch_login',
/** for login, .token is undefined. `this` is not defined. */
token = /*this.token ||*/ 'null',
resultP = fetch(germinateServerURL + '/token', {
"credentials": "include",
"headers": {
"Accept": "application/json, text/plain, */*",
// "Accept-Language": "en_GB",
"Content-Type": "application/json; charset=utf-8",
"Authorization": "Bearer null",
/*
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin"
*/
},
"referrer": germinateServerDomain + '/',
body : JSON.stringify(body),
"method": "POST",
"mode": "cors"
});
return resultP;
}
/**
* @param this Germinate
* @param body bent() does not require body to be string - it will JSON.stringify().
*/
function fetchEndpoint_bent(endpoint, method = 'GET', body = undefined) {
const fnName = 'fetchEndpoint_bent';
console.log(fnName, 'send request', endpoint);
const
// maybe pass options {credentials, mode} to bent() ?
getJSON = bent(serverURL, method, 'json'),
token = this.token || 'null',
headers = // {'Authorization' : this.accessToken};
{
// 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0',
'Accept': '*/*',
// 'Accept-Language': 'en-US,en;q=0.5',
'Content-Type': 'application/json;charset=utf-8',
'Authorization': 'Bearer ' + token,
/*
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
'Cache-Control': 'max-age=0'
*/
},
// new Headers( headers )
/** bent accepts body as obj or json. */
promise = getJSON('/' + endpoint, body, headers)
.catch(error => { console.log(fnName, error); throw error; });
return promise;
}
//------------------------------------------------------------------------------
Germinate.prototype.login = login;
function login(username_, password_) {
let tokenP;
const
fnName = 'login',
username = username_ || this.username || (isNodeJs ? env.germinate_username : env.username),
password = password_ || this.password || (isNodeJs ? env.germinate_password : env.password);
if (username && password) {
const
body = {username, password},
method = 'POST',
isSouthGreen = this.serverURL.includes('gigwa.ird.fr'),
endpoint = isSouthGreen ? 'gigwa/generateToken' : 'token';
if (this.token) {
console.log(fnName, this.token);
debugger;
}
tokenP =
// fetchEndpoint_fetch_login(endpoint, method, body)
this.fetchEndpoint(endpoint, method, body)
.then(obj => {
console.log(fnName, obj);
return obj.token;
})
.catch(err => {
console.log(fnName, err);
return null;
});
} else {
tokenP = Promise.resolve(undefined);
}
return tokenP;
}
//------------------------------------------------------------------------------
Germinate.prototype.maps = maps;
/** Get the list of genotype datasets.
*/
function maps() {
const
promise = this.fetchEndpoint(brapi_v + '/maps');
return promise;
}
Germinate.prototype.linkagegroups = linkagegroups;
/** Get the list of linkagegroups (e.g. chromosomes) of a map (i.e. dataset).
*/
function linkagegroups(mapDbId) {
/** refn :
* https://brapigenotyping21.docs.apiary.io/#/reference/genome-maps/get-maps-map-db-id-linkagegroups
* /maps/{mapDbId}/linkagegroups
*/
const
promise =
this.fetchEndpoint(brapi_v + '/maps/' + mapDbId + '/linkagegroups');
return promise;
}
Germinate.prototype.markers = markers;
function markers() {
this.fetchEndpoint('marker/table', 'POST')
.then(response => {
console.log(response);
});
}
Germinate.prototype.callsets = callsets;
function callsets(data) {
this.brapi_root
.data(data)
.callsets()
.all(function(genotype_objects){
console.log(genotype_objects);
});
}
Germinate.prototype.germplasm = germplasm;
function germplasm() {
this.brapi_root
.germplasm()
.all(function(objects){
console.log(objects);
});
}
Germinate.prototype.samples = samples;
function samples(dataset) {
const samplesP =
this.fetchEndpoint(brapi_v + '/' + 'callsets/dataset' + '/' + dataset);
/*
.then(response => {
console.log(response);
})*/
return samplesP;
}
function isDefined(x) {
return (x !== undefined) && (x !== null);
}
Germinate.prototype.callsetsCalls = callsetsCalls;
/* filtering based on positions and chromosome:
* GET
* {domain}/api/brapi/v2/callsets/{callSetDbId}/calls/mapid/{mapid}/chromosome/{chromosome}/position/{positionStart}/{positionEnd}
* example: {domain}/api/brapi/v2/callsets/4-1036/calls/mapid/4/chromosome/10/position/1/300
*
* @param mapid, callSetDbId, start, end
* e.g. '1', '1-593', '2932022', '2932028'
* @param limit_result optional rowLimit / nLines (Spark server may 404 without this,
* and Pretzel Germinate does 404 if it is included)
*/
function callsetsCalls(mapid, callSetDbId, linkageGroupName, start, end, limit_result) {
const fnName = 'callsetsCalls';
/** Optional location / position / variantName interval to filter SNPs */
let intervalParams = '';
if (isDefined(linkageGroupName)) {
intervalParams += '/chromosome/' + linkageGroupName;
}
if (isDefined(start)) {
intervalParams += '/position/' + start;
if (isDefined(end)) {
intervalParams += '/' + end;
}
}
const serverTypeIsSpark = serverURL.endsWith('/spark/api');
if (serverTypeIsSpark && isDefined(limit_result)) {
intervalParams += '/' + limit_result;
}
const
endpoint = brapi_v + '/' + 'callsets' + '/' + callSetDbId + '/calls'
+ '/mapid/' + mapid + intervalParams,
callsP = this.fetchEndpoint(endpoint);
if (trace) {
console.log(fnName, {serverURL, endpoint});
}
/*
.then(response => {
console.log(response);
})
*/
return callsP;
}
//------------------------------------------------------------------------------