1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | import "source-map-support/register";
|
7 |
|
8 | declare global {
|
9 |
|
10 | namespace NodeJS {
|
11 | interface Global {
|
12 | XMLHttpRequest: XMLHttpRequest;
|
13 | }
|
14 | }
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | }
|
20 |
|
21 | if (!global.hasOwnProperty("XMLHttpRequest")) {
|
22 | global.XMLHttpRequest = require("xhr2");
|
23 | }
|
24 |
|
25 | import { curry, negate, uniq } from "lodash/fp";
|
26 | import {
|
27 | camelCase,
|
28 | defaultsDeep,
|
29 | fill,
|
30 | invert,
|
31 | isArray,
|
32 | isEmpty,
|
33 | isNaN,
|
34 | isNull,
|
35 | isString,
|
36 | isUndefined,
|
37 | omitBy,
|
38 | toPairs,
|
39 | zip
|
40 | } from "lodash";
|
41 | import { Observable } from "rxjs/Observable";
|
42 |
|
43 | import { AjaxRequest } from "rxjs/observable/dom/AjaxObservable";
|
44 | import "rxjs/add/observable/dom/ajax";
|
45 | import "rxjs/add/observable/empty";
|
46 | import "rxjs/add/observable/forkJoin";
|
47 | import "rxjs/add/observable/from";
|
48 | import "rxjs/add/observable/throw";
|
49 | import "rxjs/add/observable/zip";
|
50 | import "rxjs/add/operator/buffer";
|
51 | import "rxjs/add/operator/bufferWhen";
|
52 | import "rxjs/add/operator/catch";
|
53 | import "rxjs/add/operator/concatAll";
|
54 | import "rxjs/add/operator/debounceTime";
|
55 | import "rxjs/add/operator/delay";
|
56 | import "rxjs/add/operator/distinctUntilChanged";
|
57 | import "rxjs/add/operator/do";
|
58 | import "rxjs/add/operator/filter";
|
59 | import "rxjs/add/operator/find";
|
60 | import "rxjs/add/operator/mergeMap";
|
61 | import "rxjs/add/operator/map";
|
62 | import "rxjs/add/operator/multicast";
|
63 | import "rxjs/add/operator/publishReplay";
|
64 | import "rxjs/add/operator/race";
|
65 | import "rxjs/add/operator/reduce";
|
66 | import "rxjs/add/operator/skip";
|
67 | import "rxjs/add/operator/toArray";
|
68 | import "rx-extra/add/operator/throughNodeStream";
|
69 | import { Subject } from "rxjs/Subject";
|
70 | import { TSVGetter } from "./spinoffs/TSVGetter";
|
71 | import { dataTypeParsers } from "./spinoffs/dataTypeParsers";
|
72 | import { arrayify, unionLSV } from "./spinoffs/jsonld-utils";
|
73 | const VError = require("verror");
|
74 |
|
75 | const BDB = "http://vocabularies.bridgedb.org/ops#";
|
76 | const BIOPAX = "http://www.biopax.org/release/biopax-level3.owl#";
|
77 | const IDENTIFIERS = "http://identifiers.org/";
|
78 | const OWL = "http://www.w3.org/2002/07/owl#";
|
79 | const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
|
80 |
|
81 | const CSV_OPTIONS = { objectMode: true, delimiter: "\t" };
|
82 |
|
83 |
|
84 |
|
85 | const XREF_REQUEST_DEBOUNCE_TIME = 10;
|
86 | const XREF_REQUEST_CHUNK_SIZE = 100;
|
87 |
|
88 | const BRIDGE_DB_REPO_CDN =
|
89 | "https://raw.githubusercontent.com/bridgedb/BridgeDb/";
|
90 | const BRIDGE_DB_COMMIT_HASH = "465f9f944d09cefbb167eceb9c69499a764100a2";
|
91 | export const CONFIG_DEFAULT = {
|
92 | baseIri: "https://webservice.bridgedb.org/",
|
93 | context: [
|
94 | BRIDGE_DB_REPO_CDN,
|
95 | BRIDGE_DB_COMMIT_HASH,
|
96 | "/org.bridgedb.bio/resources/org/bridgedb/bio/jsonld-context.jsonld"
|
97 | ].join(""),
|
98 | dataSourcesMetadataHeadersIri: [
|
99 | BRIDGE_DB_REPO_CDN,
|
100 | BRIDGE_DB_COMMIT_HASH,
|
101 | "/org.bridgedb.bio/resources/org/bridgedb/bio/datasources_headers.txt"
|
102 | ].join(""),
|
103 | dataSourcesMetadataIri: [
|
104 | BRIDGE_DB_REPO_CDN,
|
105 | BRIDGE_DB_COMMIT_HASH,
|
106 | "/org.bridgedb.bio/resources/org/bridgedb/bio/datasources.txt"
|
107 | ].join(""),
|
108 | http: {
|
109 | timeout: 4 * 1000,
|
110 | retryLimit: 2,
|
111 | retryDelay: 3 * 1000
|
112 | }
|
113 | };
|
114 |
|
115 |
|
116 |
|
117 | const DATASOURCE_ID_PROPERTIES = [
|
118 | "id",
|
119 | "miriamUrn",
|
120 | "conventionalName",
|
121 | "preferredPrefix",
|
122 | "systemCode"
|
123 | ];
|
124 |
|
125 | const IRI_TO_NAME = {
|
126 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#about": "id",
|
127 | "http://identifiers.org/idot/preferredPrefix": "preferredPrefix",
|
128 | "http://identifiers.org/miriam.collection/": "miriamUrn"
|
129 | };
|
130 | const NAME_TO_IRI = invert(IRI_TO_NAME);
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | function miriamUrnToIdentifiersIri(miriamUrn: string): string {
|
139 | const preferredPrefix = miriamUrnToPreferredPrefix(miriamUrn);
|
140 | if (preferredPrefix) {
|
141 | return IDENTIFIERS + preferredPrefix + "/";
|
142 | }
|
143 | }
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | function miriamUrnToPreferredPrefix(miriamUrn: string): string {
|
152 |
|
153 |
|
154 | if (miriamUrn.indexOf("urn:miriam:") > -1) {
|
155 | return miriamUrn.substring(11, miriamUrn.length);
|
156 | }
|
157 | }
|
158 |
|
159 | export interface DataSourcesMetadataHeaderRow {
|
160 | header: string;
|
161 | description: string;
|
162 | example_entry: string;
|
163 | id: string;
|
164 | name: string;
|
165 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#datatype": string;
|
166 | }
|
167 |
|
168 | export class BridgeDb {
|
169 | config;
|
170 | dataSourceMappings$;
|
171 |
|
172 | getTSV;
|
173 | private xrefsRequestQueue;
|
174 | private xrefsResponseQueue;
|
175 | constructor(config: Partial<typeof CONFIG_DEFAULT> = CONFIG_DEFAULT) {
|
176 | let bridgeDb = this;
|
177 | defaultsDeep(config, CONFIG_DEFAULT);
|
178 | bridgeDb.config = config;
|
179 |
|
180 | var xrefsRequestQueue = (bridgeDb.xrefsRequestQueue = new Subject());
|
181 | var debounceSignel = xrefsRequestQueue.debounceTime(
|
182 | XREF_REQUEST_DEBOUNCE_TIME
|
183 | );
|
184 |
|
185 | bridgeDb.xrefsResponseQueue = xrefsRequestQueue
|
186 | .filter(
|
187 | ({ organism, xrefDataSource, xrefIdentifier }) =>
|
188 | !isEmpty(organism) &&
|
189 | !isEmpty(xrefDataSource) &&
|
190 | !isEmpty(xrefIdentifier)
|
191 | )
|
192 | |
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 | .bufferWhen(() =>
|
202 | Observable.race(debounceSignel, xrefsRequestQueue.skip(2000))
|
203 | )
|
204 | .filter(x => !isEmpty(x))
|
205 | .mergeMap(function(
|
206 | inputs: {
|
207 | organism: organism;
|
208 | xrefDataSource: string;
|
209 | xrefIdentifier: string;
|
210 | desiredXrefDataSources?: string[];
|
211 | }[]
|
212 | ) {
|
213 | const firstInput = inputs[0];
|
214 | const organism = firstInput.organism;
|
215 | const xrefDataSources = inputs.map(input => input.xrefDataSource);
|
216 | const xrefIdentifiers = inputs.map(input => input.xrefIdentifier);
|
217 | const desiredXrefDataSources = firstInput.desiredXrefDataSources;
|
218 | return bridgeDb.xrefsBatch(
|
219 | organism,
|
220 | xrefDataSources,
|
221 | xrefIdentifiers,
|
222 | desiredXrefDataSources
|
223 | );
|
224 | })
|
225 | .multicast(new Subject());
|
226 |
|
227 |
|
228 | bridgeDb.xrefsResponseQueue.connect();
|
229 |
|
230 | const getTSV = (bridgeDb.getTSV = new TSVGetter(config.http).get);
|
231 |
|
232 | const dataSourcesMetadataHeaders$ = getTSV(
|
233 | config.dataSourcesMetadataHeadersIri
|
234 | ).map(function(fields): DataSourcesMetadataHeaderRow {
|
235 | const id = fields[4];
|
236 | return {
|
237 |
|
238 |
|
239 |
|
240 | header: fields[1],
|
241 | description: fields[2],
|
242 | example_entry: fields[3],
|
243 | id: id,
|
244 | name: IRI_TO_NAME.hasOwnProperty(id)
|
245 | ? IRI_TO_NAME[id]
|
246 | : id.split(/[\/|#]/).pop(),
|
247 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#datatype": fields[5]
|
248 | };
|
249 | });
|
250 |
|
251 | bridgeDb.dataSourceMappings$ = Observable.forkJoin(
|
252 | dataSourcesMetadataHeaders$.toArray(),
|
253 | getTSV(config.dataSourcesMetadataIri).toArray()
|
254 | )
|
255 | .mergeMap(function(results) {
|
256 | var metadataByColumnIndex = results[0];
|
257 | var rows = results[1];
|
258 |
|
259 | return Observable.from(rows).map(function(fields) {
|
260 | return fields.reduce(
|
261 | function(acc, field, i) {
|
262 | const metadata = metadataByColumnIndex[i];
|
263 | const { id, name } = metadata;
|
264 |
|
265 | if (!!id && !(id in IRI_TO_NAME)) {
|
266 | IRI_TO_NAME[id] = name;
|
267 | NAME_TO_IRI[name] = id;
|
268 | }
|
269 | acc[name] = dataTypeParsers[metadata[RDF + "datatype"]](field);
|
270 | return acc;
|
271 | },
|
272 | {} as DataSource
|
273 | );
|
274 | });
|
275 | })
|
276 | .map(function(dataSource: DataSource): DataSource {
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 | return omitBy(dataSource, function(value: any): boolean {
|
285 | return (
|
286 | value === "" || isNaN(value) || isNull(value) || isUndefined(value)
|
287 | );
|
288 | }) as DataSource;
|
289 | })
|
290 | .map(function(dataSource: DataSource) {
|
291 |
|
292 |
|
293 | if (dataSource.id === "Sp") {
|
294 | dataSource.id = "urn:miriam:uniprot";
|
295 | }
|
296 |
|
297 |
|
298 |
|
299 | if (
|
300 | dataSource.hasOwnProperty("id") &&
|
301 | dataSource.id.indexOf("urn:miriam:") > -1
|
302 | ) {
|
303 |
|
304 | const miriamUrn = dataSource.id;
|
305 | dataSource.miriamUrn = miriamUrn;
|
306 | const preferredPrefix = miriamUrnToPreferredPrefix(miriamUrn);
|
307 | if (preferredPrefix) {
|
308 | dataSource.preferredPrefix = preferredPrefix;
|
309 |
|
310 | dataSource.sameAs = dataSource.sameAs || [];
|
311 | dataSource.sameAs.push(miriamUrn);
|
312 |
|
313 | const identifiersIri = miriamUrnToIdentifiersIri(miriamUrn);
|
314 | if (identifiersIri) {
|
315 | dataSource.id = dataSource.hasIdentifiersOrgPattern = identifiersIri;
|
316 | }
|
317 | }
|
318 | } else {
|
319 | delete dataSource.id;
|
320 | }
|
321 | return dataSource;
|
322 | })
|
323 | .map(function(dataSource: DataSource) {
|
324 | const primaryUriPattern = dataSource.hasPrimaryUriPattern;
|
325 | if (!!primaryUriPattern) {
|
326 | const regexXrefIdentifierPattern = dataSource.hasRegexPattern || ".*";
|
327 |
|
328 | dataSource.hasRegexUriPattern = primaryUriPattern.replace(
|
329 | "$id",
|
330 |
|
331 | "(" + regexXrefIdentifierPattern.replace(/(^\^|\$$)/g, "") + ")"
|
332 | );
|
333 |
|
334 |
|
335 | var indexOfDollaridWhenAtEnd = primaryUriPattern.length - 3;
|
336 | if (primaryUriPattern.indexOf("$id") === indexOfDollaridWhenAtEnd) {
|
337 | dataSource.sameAs = dataSource.sameAs || [];
|
338 | dataSource.sameAs.push(
|
339 | primaryUriPattern.substr(0, indexOfDollaridWhenAtEnd)
|
340 | );
|
341 | }
|
342 | }
|
343 |
|
344 | if (dataSource.type) {
|
345 | dataSource[BDB + "type"] = dataSource.type;
|
346 | }
|
347 | dataSource.type = "Dataset";
|
348 |
|
349 | return dataSource;
|
350 | })
|
351 | .map(function(dataSource) {
|
352 | const bdbType = dataSource[BDB + "type"];
|
353 | if (!!bdbType) {
|
354 | dataSource.subject = [];
|
355 | |
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 | if (
|
376 | bdbType === "gene" ||
|
377 |
|
378 | bdbType === "probe" ||
|
379 | dataSource.preferredPrefix === "go"
|
380 | ) {
|
381 | dataSource.subject.push("GeneProduct");
|
382 | dataSource.subject.push(BIOPAX + "DnaReference");
|
383 | } else if (bdbType === "rna") {
|
384 | dataSource.subject.push("Rna");
|
385 | dataSource.subject.push(BIOPAX + "RnaReference");
|
386 | } else if (bdbType === "protein") {
|
387 | dataSource.subject.push("Protein");
|
388 | dataSource.subject.push(BIOPAX + "ProteinReference");
|
389 | } else if (bdbType === "metabolite") {
|
390 | dataSource.subject.push("Metabolite");
|
391 | dataSource.subject.push(BIOPAX + "SmallMoleculeReference");
|
392 | } else if (bdbType === "pathway") {
|
393 |
|
394 |
|
395 | dataSource.subject.push("Pathway");
|
396 | dataSource.subject.push(BIOPAX + "Pathway");
|
397 | } else if (bdbType === "ontology") {
|
398 | dataSource.subject.push(OWL + "Ontology");
|
399 | } else if (bdbType === "interaction") {
|
400 | dataSource.subject.push("Interaction");
|
401 | dataSource.subject.push(BIOPAX + "Interaction");
|
402 | }
|
403 | }
|
404 |
|
405 | dataSource.alternatePrefix = [dataSource.systemCode];
|
406 |
|
407 | return dataSource;
|
408 | })
|
409 | .reduce(function(acc, dataSource) {
|
410 | DATASOURCE_ID_PROPERTIES.forEach(function(propertyName) {
|
411 | const propertyValue = dataSource[propertyName];
|
412 | const propertyId = NAME_TO_IRI[propertyName];
|
413 | dataSource[propertyId] = propertyValue;
|
414 | acc[propertyValue] = dataSource;
|
415 | });
|
416 | return acc;
|
417 | }, {})
|
418 | .catch(err => {
|
419 | throw new VError(err, "Setting up dataSourceMappings$ in constructor");
|
420 | })
|
421 | .publishReplay();
|
422 |
|
423 |
|
424 | bridgeDb.dataSourceMappings$.connect();
|
425 | }
|
426 |
|
427 | attributes(
|
428 | organism: organism,
|
429 | xrefDataSource: string,
|
430 | xrefIdentifier: string
|
431 | ) {
|
432 | let bridgeDb = this;
|
433 | return bridgeDb
|
434 | .getTSV(
|
435 | bridgeDb.config.baseIri +
|
436 | organism +
|
437 | "/attributes/" +
|
438 | xrefDataSource +
|
439 | "/" +
|
440 | xrefIdentifier
|
441 | )
|
442 | .reduce(function(acc, fields) {
|
443 | const key = camelCase(fields[0]);
|
444 | const value = fields[1];
|
445 | acc[key] = value;
|
446 | return acc;
|
447 | }, {})
|
448 | .catch(err => {
|
449 | throw new VError(err, "calling bridgedb.attributes");
|
450 | });
|
451 | }
|
452 |
|
453 | attributeSearch(
|
454 | organism: organism,
|
455 | query: string,
|
456 | attrName?: string
|
457 | ): Observable<Xref> {
|
458 | let bridgeDb = this;
|
459 | const attrNameParamSection = attrName ? "?attrName=" + attrName : "";
|
460 | return bridgeDb
|
461 | .getTSV(
|
462 | bridgeDb.config.baseIri +
|
463 | organism +
|
464 | "/attributeSearch/" +
|
465 | query +
|
466 | attrNameParamSection
|
467 | )
|
468 | .mergeMap(bridgeDb.parseXrefRow)
|
469 | .catch(err => {
|
470 | throw new VError(err, "calling bridgedb.attributeSearch");
|
471 | });
|
472 | }
|
473 |
|
474 | attributeSet(organism: organism): Observable<string[]> {
|
475 | let bridgeDb = this;
|
476 | return bridgeDb
|
477 | .getTSV(bridgeDb.config.baseIri + organism + "/attributeSet")
|
478 | .reduce(function(acc, row) {
|
479 | acc.push(row[0]);
|
480 | return acc;
|
481 | }, [])
|
482 | .catch(err => {
|
483 | throw new VError(err, "calling bridgedb.attributeSet");
|
484 | });
|
485 | }
|
486 |
|
487 | convertXrefDataSourceTo: Function = curry(
|
488 | (targetType: string, input: string): Observable<string> => {
|
489 | let bridgeDb = this;
|
490 | return bridgeDb.dataSourceMappings$
|
491 | .map(function(mapping) {
|
492 | return !!mapping[input] && mapping[input][targetType];
|
493 | })
|
494 | .catch(err => {
|
495 | throw new VError(err, "calling bridgedb.convertXrefDataSourceTo");
|
496 | });
|
497 | }
|
498 | );
|
499 |
|
500 | identifyHeaderNameForXrefDataSource = (input: string): Observable<string> => {
|
501 | let bridgeDb = this;
|
502 | return bridgeDb.dataSourceMappings$
|
503 | .map(mapping => mapping[input])
|
504 | .filter(negate(isEmpty))
|
505 | .map(dataSource => {
|
506 | return toPairs(dataSource)
|
507 | .filter(([key, value]) => value === input)
|
508 | .map(([key, value]) => key)
|
509 | .reduce(function(acc: string, key: string): string {
|
510 |
|
511 | return acc.length > key.length ? acc : key;
|
512 | });
|
513 | })
|
514 | .catch(err => {
|
515 | throw new VError(
|
516 | err,
|
517 | "calling bridgedb.identifyHeaderNameForXrefDataSource"
|
518 | );
|
519 | });
|
520 | };
|
521 |
|
522 | dataSourceProperties = (input: string): Observable<DataSource> => {
|
523 | let bridgeDb = this;
|
524 | return bridgeDb.dataSourceMappings$
|
525 | .map(mapping => mapping[input])
|
526 | .catch(err => {
|
527 | throw new VError(err, "calling bridgedb.dataSourceProperties");
|
528 | });
|
529 | };
|
530 |
|
531 | isFreeSearchSupported(organism: organism): Observable<boolean> {
|
532 | let bridgeDb = this;
|
533 |
|
534 | const ajaxRequest: AjaxRequest = {
|
535 | url: bridgeDb.config.baseIri + organism + "/isFreeSearchSupported",
|
536 | method: "GET",
|
537 | responseType: "text",
|
538 | timeout: bridgeDb.config.http.timeout,
|
539 | crossDomain: true
|
540 | };
|
541 | return (
|
542 | Observable.ajax(ajaxRequest)
|
543 | .map((ajaxResponse): string => ajaxResponse.xhr.response)
|
544 |
|
545 | .map(res => res === "true")
|
546 |
|
547 | .catch(
|
548 | (err): Observable<any> => {
|
549 | throw new VError(err, "calling bridgedb.isFreeSearchSupported");
|
550 | }
|
551 | )
|
552 | );
|
553 | }
|
554 |
|
555 | isMappingSupported(
|
556 | organism: organism,
|
557 | sourceXrefDataSource: string,
|
558 | targetXrefDataSource: string
|
559 | ): Observable<boolean> {
|
560 | let bridgeDb = this;
|
561 |
|
562 | const ajaxRequest: AjaxRequest = {
|
563 | url: `${bridgeDb.config.baseIri +
|
564 | organism}/isMappingSupported/${sourceXrefDataSource}/${targetXrefDataSource}`,
|
565 | method: "GET",
|
566 | responseType: "text",
|
567 | timeout: bridgeDb.config.http.timeout,
|
568 | crossDomain: true
|
569 | };
|
570 | return (
|
571 | Observable.ajax(ajaxRequest)
|
572 | .map((ajaxResponse): string => ajaxResponse.xhr.response)
|
573 |
|
574 | .map(res => res === "true")
|
575 |
|
576 | .catch(
|
577 | (err): Observable<any> => {
|
578 | throw new VError(err, "calling bridgedb.isMappingSupported");
|
579 | }
|
580 | )
|
581 | );
|
582 | }
|
583 |
|
584 | organismProperties(organism: organism): Observable<{}> {
|
585 | let bridgeDb = this;
|
586 | return bridgeDb
|
587 | .getTSV(bridgeDb.config.baseIri + organism + "/properties")
|
588 | .reduce(function(acc, fields) {
|
589 | const key = camelCase(fields[0]);
|
590 | const value = fields[1];
|
591 | acc[key] = value;
|
592 | return acc;
|
593 | }, {})
|
594 | .catch(err => {
|
595 | throw new VError(err, "calling bridgedb.organismProperties");
|
596 | });
|
597 | }
|
598 |
|
599 | organisms(): Observable<{}> {
|
600 | let bridgeDb = this;
|
601 | return bridgeDb
|
602 | .getTSV(bridgeDb.config.baseIri + "contents")
|
603 | .map(function(fields) {
|
604 | return {
|
605 | en: fields[0],
|
606 | la: fields[1]
|
607 | };
|
608 | })
|
609 | .catch(err => {
|
610 | throw new VError(err, "calling bridgedb.organisms");
|
611 | });
|
612 | }
|
613 |
|
614 | private parseXrefRow = ([
|
615 | xrefIdentifier,
|
616 | dataSourceConventionalName,
|
617 | symbol
|
618 | ]: [string, string, string | undefined]): Observable<Xref> => {
|
619 | let bridgeDb = this;
|
620 | if (!xrefIdentifier || !dataSourceConventionalName) {
|
621 | return Observable.empty();
|
622 | }
|
623 |
|
624 | return bridgeDb.dataSourceMappings$
|
625 | .map(mapping => mapping[dataSourceConventionalName])
|
626 | .map(function(dataSource: DataSource) {
|
627 | let xref: Xref = {
|
628 | xrefIdentifier: xrefIdentifier,
|
629 | isDataItemIn: dataSource
|
630 | };
|
631 |
|
632 | if (symbol) {
|
633 | xref.symbol = symbol;
|
634 | }
|
635 |
|
636 | if (dataSource.hasOwnProperty("id")) {
|
637 | xref.id = encodeURI(dataSource.id + xref.xrefIdentifier);
|
638 | }
|
639 |
|
640 | return xref;
|
641 | });
|
642 | };
|
643 |
|
644 | search(organism: organism, query: string): Observable<Xref> {
|
645 | let bridgeDb = this;
|
646 | return bridgeDb
|
647 | .getTSV(bridgeDb.config.baseIri + organism + "/search/" + query)
|
648 | .mergeMap(bridgeDb.parseXrefRow)
|
649 | .catch(err => {
|
650 | throw new VError(err, "calling bridgedb.search");
|
651 | });
|
652 | }
|
653 |
|
654 | sourceDataSources(organism: organism): Observable<DataSource> {
|
655 | let bridgeDb = this;
|
656 | return bridgeDb
|
657 | .getTSV(bridgeDb.config.baseIri + organism + "/sourceDataSources")
|
658 | .map(function(fields) {
|
659 | return fields[0];
|
660 | })
|
661 | .mergeMap(bridgeDb.dataSourceProperties)
|
662 | .catch(err => {
|
663 | throw new VError(err, "calling bridgedb.sourceDataSources");
|
664 | });
|
665 | }
|
666 |
|
667 | targetDataSources(organism: organism): Observable<DataSource> {
|
668 | let bridgeDb = this;
|
669 | return bridgeDb
|
670 | .getTSV(bridgeDb.config.baseIri + organism + "/targetDataSources")
|
671 | .map(function(fields) {
|
672 | return fields[0];
|
673 | })
|
674 | .mergeMap(bridgeDb.dataSourceProperties)
|
675 | .catch(err => {
|
676 | throw new VError(err, "calling bridgedb.targetDataSources");
|
677 | });
|
678 | }
|
679 |
|
680 |
|
681 |
|
682 | xrefExists(
|
683 | organism: organism,
|
684 | xrefDataSource: string,
|
685 | xrefIdentifier: string
|
686 | ): Observable<boolean> {
|
687 | let bridgeDb = this;
|
688 |
|
689 | const ajaxRequest: AjaxRequest = {
|
690 | url: `${bridgeDb.config.baseIri +
|
691 | organism}/xrefExists/${xrefDataSource}/${xrefIdentifier}`,
|
692 | method: "GET",
|
693 | responseType: "text",
|
694 | timeout: bridgeDb.config.http.timeout,
|
695 | crossDomain: true
|
696 | };
|
697 | return (
|
698 | Observable.ajax(ajaxRequest)
|
699 | .map((ajaxResponse): string => ajaxResponse.xhr.response)
|
700 |
|
701 | .map(res => res === "true")
|
702 |
|
703 | .catch(
|
704 | (err): Observable<any> => {
|
705 | throw new VError(err, "calling bridgedb.xrefExists");
|
706 | }
|
707 | )
|
708 | );
|
709 | }
|
710 |
|
711 | xrefs(
|
712 | organism: organism,
|
713 | xrefDataSource: string,
|
714 | xrefIdentifier: string,
|
715 | desiredXrefDataSourceOrSources?: string
|
716 | ): Observable<Xref> {
|
717 | let bridgeDb = this;
|
718 | let xrefsRequestQueue = bridgeDb.xrefsRequestQueue;
|
719 | let xrefsResponseQueue = bridgeDb.xrefsResponseQueue;
|
720 | const desiredXrefDataSources = arrayify(desiredXrefDataSourceOrSources);
|
721 |
|
722 | xrefsRequestQueue.next({
|
723 | organism,
|
724 | xrefDataSource,
|
725 | xrefIdentifier,
|
726 | desiredXrefDataSources
|
727 | });
|
728 |
|
729 | return (
|
730 | xrefsResponseQueue
|
731 | .find(function(xrefBatchEnvelope) {
|
732 | return (
|
733 | xrefBatchEnvelope.organism === organism &&
|
734 |
|
735 |
|
736 |
|
737 |
|
738 | xrefBatchEnvelope.inputXrefDataSource === xrefDataSource &&
|
739 | xrefBatchEnvelope.inputXrefIdentifier === xrefIdentifier &&
|
740 | xrefBatchEnvelope.desiredXrefDataSources.join() ===
|
741 | desiredXrefDataSources.join()
|
742 | );
|
743 | })
|
744 | .map(x => x.xrefs)
|
745 |
|
746 | .catch(err => {
|
747 | throw new VError(err, "calling bridgedb.xrefs");
|
748 | })
|
749 | );
|
750 | }
|
751 |
|
752 | xrefsBatch = (
|
753 | organism: organism,
|
754 | oneOrMoreXrefDataSources: string | string[],
|
755 | xrefIdentifiers: string[],
|
756 | desiredXrefDataSourceOrSources?: string | string[]
|
757 | ): Observable<{
|
758 | organism: string;
|
759 | inputXrefIdentifier: string;
|
760 | inputXrefDataSource: string;
|
761 | xrefs: Xref[];
|
762 | }> => {
|
763 | let bridgeDb = this;
|
764 | const desiredXrefDataSources = arrayify(
|
765 | desiredXrefDataSourceOrSources
|
766 | ) as string[];
|
767 | const dataSourceFilterParamSection =
|
768 | desiredXrefDataSources.length === 1
|
769 | ? "?dataSource=" + desiredXrefDataSources[0]
|
770 | : "";
|
771 |
|
772 | const xrefDataSources = isArray(oneOrMoreXrefDataSources)
|
773 | ? oneOrMoreXrefDataSources
|
774 | : fill(new Array(xrefIdentifiers.length), oneOrMoreXrefDataSources);
|
775 |
|
776 | const convertXrefDataSourceToConventionalName = bridgeDb.convertXrefDataSourceTo(
|
777 | "conventionalName"
|
778 | );
|
779 |
|
780 | const callString = `Called xrefsBatch(
|
781 | ${organism},
|
782 | ${oneOrMoreXrefDataSources},
|
783 | ${xrefIdentifiers},
|
784 | ${desiredXrefDataSourceOrSources}
|
785 | )`;
|
786 |
|
787 | const postURL =
|
788 | bridgeDb.config.baseIri +
|
789 | organism +
|
790 | "/xrefsBatch" +
|
791 | dataSourceFilterParamSection;
|
792 |
|
793 | const inputXrefDataSourceHeaderName$ = Observable.from(xrefDataSources)
|
794 | .mergeMap(function(xrefDataSource) {
|
795 | return bridgeDb.identifyHeaderNameForXrefDataSource(xrefDataSource);
|
796 | })
|
797 | .find(isString);
|
798 |
|
799 | const desiredXrefDataSourceHeaderName$ = isEmpty(desiredXrefDataSources)
|
800 | ? inputXrefDataSourceHeaderName$
|
801 | : Observable.from(desiredXrefDataSources)
|
802 | .mergeMap(function(xrefDataSource) {
|
803 | return bridgeDb.identifyHeaderNameForXrefDataSource(xrefDataSource);
|
804 | })
|
805 | .find(isString);
|
806 |
|
807 | const dataSourceConventionalNames$ = Observable.from(xrefDataSources)
|
808 | .mergeMap(function(xrefDataSource) {
|
809 | return convertXrefDataSourceToConventionalName(xrefDataSource);
|
810 | })
|
811 | .toArray();
|
812 |
|
813 | return Observable.forkJoin(
|
814 | inputXrefDataSourceHeaderName$,
|
815 | desiredXrefDataSourceHeaderName$,
|
816 | dataSourceConventionalNames$
|
817 | ).mergeMap(function([
|
818 | inputXrefDataSourceHeaderName,
|
819 | desiredXrefDataSourceHeaderName,
|
820 | dataSourceConventionalNames
|
821 | ]) {
|
822 |
|
823 |
|
824 |
|
825 | const body = uniq(
|
826 | zip(xrefIdentifiers, dataSourceConventionalNames)
|
827 | .filter(pair => !!pair[1])
|
828 | .map(x => x.join("\t"))
|
829 | ).join("\n");
|
830 |
|
831 | if (isEmpty(body.replace(/[\ \n\t]/g, ""))) {
|
832 | return Observable.throw(
|
833 | new Error(`Error: body is empty. ${callString}`)
|
834 | );
|
835 | }
|
836 |
|
837 | const convertXrefDataSourceToInputFormat = bridgeDb.convertXrefDataSourceTo(
|
838 | inputXrefDataSourceHeaderName
|
839 | );
|
840 | const convertXrefDataSourceToDesiredInputFormat = bridgeDb.convertXrefDataSourceTo(
|
841 | desiredXrefDataSourceHeaderName
|
842 | );
|
843 |
|
844 | return bridgeDb
|
845 | .getTSV(postURL, "POST", body)
|
846 | .mergeMap(function(xrefStringsByInput) {
|
847 | const inputXrefIdentifier = xrefStringsByInput[0];
|
848 | const inputXrefDataSource = xrefStringsByInput[1];
|
849 | const xrefsString = xrefStringsByInput[2];
|
850 |
|
851 |
|
852 |
|
853 | return Observable.from(xrefsString.split(","))
|
854 | .mergeMap(function(
|
855 | xrefString: string
|
856 | ): Observable<Record<"xrefDataSource" | "xrefIdentifier", string>> {
|
857 | if (xrefString === "N/A") {
|
858 | return Observable.empty();
|
859 | }
|
860 |
|
861 |
|
862 |
|
863 | const [
|
864 | returnedXrefDataSource,
|
865 | returnedXrefIdentifier
|
866 | ] = xrefString.split(/:(.+)/);
|
867 |
|
868 | return convertXrefDataSourceToDesiredInputFormat(
|
869 | returnedXrefDataSource
|
870 | ).map(function(desiredXrefDataSource) {
|
871 | return {
|
872 | xrefDataSource: desiredXrefDataSource,
|
873 | xrefIdentifier: returnedXrefIdentifier
|
874 | };
|
875 | });
|
876 | })
|
877 | .filter(({ xrefDataSource }) => {
|
878 | return (
|
879 | !isEmpty(xrefDataSource) &&
|
880 | (desiredXrefDataSources.length === 0 ||
|
881 | desiredXrefDataSources.indexOf(xrefDataSource) > -1)
|
882 | );
|
883 | })
|
884 | .toArray()
|
885 | .mergeMap(function(xrefs) {
|
886 | if (desiredXrefDataSources.length > 0) {
|
887 |
|
888 |
|
889 | xrefs.sort(function(a, b) {
|
890 | const aIndex = desiredXrefDataSources.indexOf(
|
891 | a.xrefDataSource
|
892 | );
|
893 | const bIndex = desiredXrefDataSources.indexOf(
|
894 | b.xrefDataSource
|
895 | );
|
896 | if (aIndex < bIndex) {
|
897 | return -1;
|
898 | } else if (aIndex > bIndex) {
|
899 | return 1;
|
900 | } else {
|
901 | return 0;
|
902 | }
|
903 | });
|
904 | }
|
905 |
|
906 | return convertXrefDataSourceToInputFormat(
|
907 | inputXrefDataSource
|
908 | ).map(function(inputXrefDataSource) {
|
909 | return {
|
910 | organism,
|
911 | inputXrefDataSource,
|
912 | inputXrefIdentifier,
|
913 | xrefs,
|
914 |
|
915 | desiredXrefDataSources
|
916 | };
|
917 | });
|
918 | });
|
919 | })
|
920 | .catch(null, function(err) {
|
921 | throw new VError(err, `Error: ${callString}`);
|
922 | });
|
923 | });
|
924 | };
|
925 | }
|