Source: data/requester.js

import { TRANSFORMS } from '../registry';

/**
 * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.
 *   It passes state information and ensures that data is formatted in the manner expected by the plot.
 *
 * This object is not part of the public interface. It should almost **never** be replaced or modified directly.
 *
 * It is also responsible for constructing a "chain" of dependent requests, by requesting each datasource
 *   sequentially in the order specified in the datalayer `fields` array. Data sources are only chained within a
 *   data layer, and only if that layer requests more than one source of data.
 * @param {DataSources} sources A set of data sources used specifically by this plot instance
 * @private
 */
class Requester {
    constructor(sources) {
        this._sources = sources;
    }

    __split_requests(fields) {
        // Given a fields array, return an object specifying what datasource names the data layer should make requests
        //  to, and how to handle the returned data
        var requests = {};
        // Regular expression finds namespace:field|trans
        var re = /^(?:([^:]+):)?([^:|]*)(\|.+)*$/;
        fields.forEach(function(raw) {
            var parts = re.exec(raw);
            var ns = parts[1] || 'base';
            var field = parts[2];
            var trans = TRANSFORMS.get(parts[3]);
            if (typeof requests[ns] == 'undefined') {
                requests[ns] = {outnames:[], fields:[], trans:[]};
            }
            requests[ns].outnames.push(raw);
            requests[ns].fields.push(field);
            requests[ns].trans.push(trans);
        });
        return requests;
    }

    /**
     * Fetch data, and create a chain that only connects two data sources if they depend on each other
     * @param {Object} state The current "state" of the plot, such as chromosome and start/end positions
     * @param {String[]} fields The list of data fields specified in the `layout` for a specific data layer
     * @returns {Promise}
     */
    getData(state, fields) {
        var requests = this.__split_requests(fields);
        // Create an array of functions that, when called, will trigger the request to the specified datasource
        var request_handles = Object.keys(requests).map((key) => {
            if (!this._sources.get(key)) {
                throw new Error(`Datasource for namespace ${key} not found`);
            }
            return this._sources.get(key).getData(
                state,
                requests[key].fields,
                requests[key].outnames,
                requests[key].trans
            );
        });
        //assume the fields are requested in dependent order
        //TODO: better manage dependencies
        var ret = Promise.resolve({header:{}, body: [], discrete: {}});
        for (var i = 0; i < request_handles.length; i++) {
            // If a single datalayer uses multiple sources, perform the next request when the previous one completes
            ret = ret.then(request_handles[i]);
        }
        return ret;
    }
}


export default Requester;