Skip to content

Commit

Permalink
fix: more comprehensive usage of types (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe authored Jan 31, 2024
1 parent 649b423 commit c1c2ff3
Show file tree
Hide file tree
Showing 22 changed files with 2,245 additions and 528 deletions.
22 changes: 17 additions & 5 deletions src/api/annonars/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import createFetchMock from 'vitest-fetch-mock'

import { LinearStrucvarImpl, SeqvarImpl } from '../../lib/genomicVars'
import { Record as GeneInfoRecord } from '../../pbs/annonars/genes/base'
import { AnnonarsClient } from './client'
import { ClinvarSvQueryResponse, GeneSearchResponse, SeqvarInfoResponse } from './types'

const geneInfoBrca1Json = JSON.parse(
fs.readFileSync(
Expand Down Expand Up @@ -82,7 +84,9 @@ describe.concurrent('AnnonarsClient.fetchVariantInfo()', () => {
const result = await client.fetchVariantInfo(seqvar)

// assert:
expect(JSON.stringify(result)).toEqual(JSON.stringify(variantInfoBrca1Json))
expect(JSON.stringify(result)).toEqual(
JSON.stringify(SeqvarInfoResponse.fromJson(variantInfoBrca1Json))
)
})

it('do removes chr prefix from chromosome if genome release is grch38', async () => {
Expand All @@ -99,7 +103,9 @@ describe.concurrent('AnnonarsClient.fetchVariantInfo()', () => {
const result = await client.fetchVariantInfo(seqvar)

// assert:
expect(JSON.stringify(result)).toEqual(JSON.stringify(variantInfoBrca1Json))
expect(JSON.stringify(result)).toEqual(
JSON.stringify(SeqvarInfoResponse.fromJson(variantInfoBrca1Json))
)
})

it('fails to fetch variant info with wrong variant', async () => {
Expand Down Expand Up @@ -179,7 +185,9 @@ describe.concurrent('AnnonarsClient.fetchGenes()', () => {
)

// assert:
expect(JSON.stringify(result)).toEqual(JSON.stringify(searchInfoInfoEmpJson))
expect(JSON.stringify(result)).toEqual(
JSON.stringify(GeneSearchResponse.fromJson(searchInfoInfoEmpJson))
)
})

it('fails to fetch genes with wrong query', async () => {
Expand Down Expand Up @@ -218,7 +226,9 @@ describe.concurrent('AnnonarsClient.fetchGeneInfos()', () => {
const result = await client.fetchGeneInfos(['BRCA1', 'BRCA2'])

// assert:
expect(JSON.stringify(result)).toMatch(JSON.stringify([geneInfoBrca1Json]))
expect(JSON.stringify(result)).toMatch(
JSON.stringify([GeneInfoRecord.fromJson(geneInfoBrca1Json)])
)
})

it('fails to fetch gene infos with wrong HGNC id', async () => {
Expand Down Expand Up @@ -254,7 +264,9 @@ describe.concurrent('AnnonarsClient.fetchClinvarStrucvars()', () => {
const result = await client.fetchClinvarStrucvars(strucVar)

// assert:
expect(JSON.stringify(result)).toMatch(JSON.stringify(clinvarStrucvarResponseBrca1Json))
expect(JSON.stringify(result)).toMatch(
JSON.stringify(ClinvarSvQueryResponse.fromJson(clinvarStrucvarResponseBrca1Json))
)
})

it('fails to overlapping ClinVar Strucvars if query fails', async () => {
Expand Down
36 changes: 23 additions & 13 deletions src/api/annonars/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { chunks } from '@reactgular/chunks'
import type { LinearStrucvar, Seqvar } from '../../lib/genomicVars'
import { ClinvarPerGeneRecord } from '../../pbs/annonars/clinvar/per_gene'
import { Record as GeneInfoRecord } from '../../pbs/annonars/genes/base'
import { GeneInfoResult } from './types'
import {
ClinvarSvQueryResponse,
GeneInfoResult,
GeneSearchResponse,
SeqvarInfoResponse
} from './types'

/** Base URL for annonars API access */
const API_BASE_URL = '/internal/proxy/annonars/'
Expand Down Expand Up @@ -35,7 +40,7 @@ export class AnnonarsClient {
*
* @param seqvar The variant to retrieve the information for.
*/
async fetchVariantInfo(seqvar: Seqvar): Promise<any> {
async fetchVariantInfo(seqvar: Seqvar): Promise<SeqvarInfoResponse> {
const { genomeBuild, chrom, pos, del, ins } = seqvar
let chromosome = chrom.replace('chr', '')
if (genomeBuild !== 'grch37') {
Expand All @@ -53,7 +58,8 @@ export class AnnonarsClient {
if (!response.ok) {
throw new Error(`failed to fetch variant info: ${response.statusText}`)
}
return await response.json()
const responseJson = await response.json()
return SeqvarInfoResponse.fromJson(responseJson)
}

/**
Expand All @@ -76,16 +82,18 @@ export class AnnonarsClient {
* Search for genes, e.g., by symbol, via annonars REST API.
*
* @param query Query string to search for.
* @returns
* @returns Promise with gene search response.
* @throws Error if the request fails.
*/
async fetchGenes(query: string): Promise<any> {
async fetchGenes(query: string): Promise<GeneSearchResponse> {
const response = await fetch(`${this.apiBaseUrl}genes/search?q=${query}`, {
method: 'GET'
})
if (!response.ok) {
throw new Error(`failed to fetch genes: ${response.statusText}`)
}
return await response.json()
const responseJson = await response.json()
return GeneSearchResponse.fromJson(responseJson)
}

/**
Expand All @@ -98,7 +106,7 @@ export class AnnonarsClient {
async fetchGeneInfos(hgncIds: string[], chunkSize?: number): Promise<GeneInfoRecord[]> {
const hgncIdChunks = chunks(hgncIds, chunkSize ?? 10)

const promises = hgncIdChunks.map(async (chunk: any) => {
const promises = hgncIdChunks.map(async (chunk) => {
const url = `${this.apiBaseUrl}genes/info?hgnc_id=${chunk.join(',')}`

const response = await fetch(url, {
Expand All @@ -111,12 +119,13 @@ export class AnnonarsClient {
})

const responses = await Promise.all(promises)
const results = await Promise.all(responses.map((response: any) => response.json()))
const resultJsons = await Promise.all(responses.map((response) => response.json()))

const result: any = []
results.forEach((chunk: any) => {
const result: GeneInfoRecord[] = []
resultJsons.forEach((chunk: any) => {
for (const value of Object.values(chunk.genes)) {
result.push(value)
// @ts-ignore
result.push(GeneInfoRecord.fromJson(value as JsonValue))
}
})
return result
Expand All @@ -129,7 +138,7 @@ export class AnnonarsClient {
strucvar: LinearStrucvar,
pageSize: number = 1000,
minOverlap: number = 0.1
): Promise<any> {
): Promise<ClinvarSvQueryResponse> {
const { genomeBuild, chrom, start, stop } = strucvar
const url =
`${this.apiBaseUrl}clinvar-sv/query?genomeRelease=${genomeBuild}&` +
Expand All @@ -142,6 +151,7 @@ export class AnnonarsClient {
if (!response.ok) {
throw new Error(`failed to fetch clinvar strucvars: ${response.statusText}`)
}
return await response.json()
const responseJson = await response.json()
return ClinvarSvQueryResponse.fromJson(responseJson)
}
}
148 changes: 145 additions & 3 deletions src/api/annonars/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { JsonValue } from '@protobuf-ts/runtime'

import { Record as ClinvarRecord } from '../../pbs/annonars/clinvar/minimal'
import { Record as ClinvarSeqvarRecord } from '../../pbs/annonars/clinvar/minimal'
import { Record as ClinvarStrucvarRecord } from '../../pbs/annonars/clinvar/sv'
import { Record as UcscConservationRecord } from '../../pbs/annonars/cons/base'
import { Record as DbsnpRecord } from '../../pbs/annonars/dbsnp/base'
import { Record as GeneInfoRecord } from '../../pbs/annonars/genes/base'
Expand All @@ -10,6 +11,147 @@ import { Record as Gnomad4Record } from '../../pbs/annonars/gnomad/gnomad4'
import { Record as GnomadMtdnaRecord } from '../../pbs/annonars/gnomad/mtdna'
import { Record as HelixmtdbRecord } from '../../pbs/annonars/helixmtdb/base'

/**
* Interface for Clinvar Strucvars query response as returned by
*/
export interface ClinvarSvQueryResponse$Api {
records: ClinvarStrucvarRecord[]
}

/**
* Interface for Clinvar Strucvars query result.
*/
export interface ClinvarSvQueryResponse {
records: ClinvarStrucvarRecord[]
}

/**
* Helper class to convert `ClinvarSvQueryResponse$Api` to `ClinvarSvQueryResponse`.
*/
class ClinvarSvQueryResponse$Type {
fromJson(apiResponse: ClinvarSvQueryResponse$Api): ClinvarSvQueryResponse {
return {
records: apiResponse.records
}
}
}

/**
* Helper instance to convert `ClinvarSvQueryResponse$Api` to `ClinvarSvQueryResponse`.
*/
export const ClinvarSvQueryResponse = new ClinvarSvQueryResponse$Type()

/**
* Interface for gene names as returned by `genes/search` API.
*/
export interface GeneNames$Api {
hgnc_id: string
symbol: string
name: string
alias_symbol: string[]
alias_name: string[]
ensembl_gene_id?: string
ncbi_gene_id?: string
}

/**
* Interface for gene names results.
*/
export interface GeneNames {
hgncId: string
symbol: string
name: string
aliasSymbol: string[]
aliasName: string[]
ensemblGeneId?: string
ncbiGeneId?: string
}

/**
* Helper class to convert `GeneNames$Api` to `GeneNames`.
*/
class GeneNames$Type {
fromJson(apiGene: GeneNames$Api): GeneNames {
return {
hgncId: apiGene.hgnc_id,
symbol: apiGene.symbol,
name: apiGene.name,
aliasSymbol: apiGene.alias_symbol,
aliasName: apiGene.alias_name,
ensemblGeneId: apiGene.ensembl_gene_id,
ncbiGeneId: apiGene.ncbi_gene_id
}
}
}

/**
* Helper instance to convert `GeneNames$Api` to `GeneNames`.
*/
export const GeneNames = new GeneNames$Type()

/**
* Interface for scored gene names as returned by `genes/search` API.
*/
export interface ScoreGeneNames$Api {
score: number
data: GeneNames$Api
}

/**
* Interface for scored gene names results.
*/
export interface ScoreGeneNames {
score: number
data: GeneNames
}

/**
* Helper class to convert `ScoreGeneNames$Api` to `ScoreGeneNames`.
*/
class ScoreGeneNames$Type {
fromJson(apiGene: ScoreGeneNames$Api): ScoreGeneNames {
return {
score: apiGene.score,
data: GeneNames.fromJson(apiGene.data)
}
}
}

/**
* Helper instance to convert `ScoreGeneNames$Api` to `ScoreGeneNames`.
*/
export const ScoreGeneNames = new ScoreGeneNames$Type()

/**
* Interface for gene search query response as returned by API.
*/
export interface GeneSearchResponse$Api {
genes: ScoreGeneNames$Api[]
}

/**
* Interface for gene search query result.
*/
export interface GeneSearchResponse {
genes: ScoreGeneNames[]
}

/**
* Helper class to convert `GeneSearchResponse$Api` to `GeneSearchResponse`.
*/
class GeneSearchResponse$Type {
fromJson(apiResponse: GeneSearchResponse$Api): GeneSearchResponse {
return {
genes: apiResponse.genes.map((gene) => ScoreGeneNames.fromJson(gene))
}
}
}

/**
* Helper instance to convert `GeneSearchResponse$Api` to `GeneSearchResponse`.
*/
export const GeneSearchResponse = new GeneSearchResponse$Type()

/**
* Interface for gene info result.
*/
Expand Down Expand Up @@ -91,7 +233,7 @@ export interface SeqvarInfoResult {
gnomadGenomes?: Gnomad2Record | Gnomad3Record | Gnomad4Record
helixmtdb?: HelixmtdbRecord
ucscConservation: UcscConservationRecord[][]
clinvar?: ClinvarRecord
clinvar?: ClinvarSeqvarRecord
}

/**
Expand Down Expand Up @@ -129,7 +271,7 @@ class SeqvarInfoResult$Type {
apiResult.clinvar === null
? undefined
: // @ts-ignore
ClinvarRecord.fromJson(apiResult.clinvar as JsonValue)
ClinvarSeqvarRecord.fromJson(apiResult.clinvar as JsonValue)
}
}
}
Expand Down
Loading

0 comments on commit c1c2ff3

Please sign in to comment.