diff --git a/package-lock.json b/package-lock.json index 87f0c1d..6f8dc49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@mdi/font": "^7.4.47", "@protobuf-ts/runtime": "^2.9.3", + "@reactgular/chunks": "^1.0.1", "@types/luxon": "^3.4.2", "luxon": "^3.4.4", "title-case": "^4.3.1", @@ -59,6 +60,7 @@ "vite-plugin-sass-dts": "^1.3.17", "vitest": "^1.2.2", "vitest-canvas-mock": "^0.3.3", + "vitest-fetch-mock": "^0.2.2", "vue-tsc": "^1.8.27" } }, @@ -4105,6 +4107,11 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@reactgular/chunks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@reactgular/chunks/-/chunks-1.0.1.tgz", + "integrity": "sha512-Ndyq5oSpkyw5MxZAhV4/fIgmtwbEgIz1L3E3hWcqkCsrlA/auw0Ags5YFTBtaneatClbe4q0J5d1cO7f/MNJkw==" + }, "node_modules/@rollup/pluginutils": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", @@ -8436,6 +8443,15 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -20315,6 +20331,21 @@ "vitest": "*" } }, + "node_modules/vitest-fetch-mock": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/vitest-fetch-mock/-/vitest-fetch-mock-0.2.2.tgz", + "integrity": "sha512-XmH6QgTSjCWrqXoPREIdbj40T7i1xnGmAsTAgfckoO75W1IEHKR8hcPCQ7SO16RsdW1t85oUm6pcQRLeBgjVYQ==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.0.6" + }, + "engines": { + "node": ">=14.14.0" + }, + "peerDependencies": { + "vitest": ">=0.16.0" + } + }, "node_modules/vitest/node_modules/@vitest/expect": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz", diff --git a/package.json b/package.json index dd5b9aa..c937ee9 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "dependencies": { "@mdi/font": "^7.4.47", "@protobuf-ts/runtime": "^2.9.3", + "@reactgular/chunks": "^1.0.1", "@types/luxon": "^3.4.2", "luxon": "^3.4.4", "title-case": "^4.3.1", @@ -97,6 +98,7 @@ "vite-plugin-sass-dts": "^1.3.17", "vitest": "^1.2.2", "vitest-canvas-mock": "^0.3.3", + "vitest-fetch-mock": "^0.2.2", "vue-tsc": "^1.8.27" } } diff --git a/src/api/mehari/client.spec.ts b/src/api/mehari/client.spec.ts new file mode 100644 index 0000000..efa4982 --- /dev/null +++ b/src/api/mehari/client.spec.ts @@ -0,0 +1,125 @@ +import fs from 'fs' +import path from 'path' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import createFetchMock from 'vitest-fetch-mock' + +import { SeqvarImpl } from '@/lib/genomicVars' + +import { LinearStrucvarImpl } from '../../lib/genomicVars' +import { MehariClient } from './client' +import { SeqvarResult, StrucvarResult } from './types' + +/** Fixture Seqvar */ +const seqvar = new SeqvarImpl('grch37', '1', 123, 'A', 'G') + +/** Fixture with BRCA1 seqvar consequence. */ +const seqvarCsqResponseBrca1 = JSON.parse( + fs.readFileSync(path.resolve(__dirname, './fixture.seqvarCsqResponse.BRCA1.json'), 'utf8') +) + +/** Fixture Strucvar affecting BRCA1 */ +const strucvar = new LinearStrucvarImpl('DEL', 'grch37', 'chr17', 43044295, 43044297) + +/** Fixture with strucvar (BRCA1) consequence */ +const strucvarCsqResponseBrca1 = JSON.parse( + fs.readFileSync(path.resolve(__dirname, './fixture.strucvarCsqResponse.BRCA1.json'), 'utf8') +) + +/** Initialize mock for `fetch()`. */ +const fetchMocker = createFetchMock(vi) + +describe.concurrent('MehariClient/seqvar', () => { + beforeEach(() => { + fetchMocker.enableMocks() + fetchMocker.resetMocks() + }) + + it('fetches TxCsq info correctly', async () => { + // arrange: + fetchMocker.mockResponseOnce(JSON.stringify(seqvarCsqResponseBrca1)) + + // act: + const client = new MehariClient() + const result = await client.retrieveSeqvarsCsq(seqvar, 'HGNC:1100') + + // assert: + expect(JSON.stringify(result)).toEqual( + JSON.stringify(SeqvarResult.fromJson(seqvarCsqResponseBrca1)) + ) + }) + + it('fetches TxCsq info correctly without HGNC id', async () => { + // arrange: + fetchMocker.mockResponseOnce(JSON.stringify(seqvarCsqResponseBrca1)) + + // act: + const client = new MehariClient() + const result = await client.retrieveSeqvarsCsq(seqvar) + + // assert: + expect(JSON.stringify(result)).toEqual( + JSON.stringify(SeqvarResult.fromJson(seqvarCsqResponseBrca1)) + ) + }) + + it('fails to fetch variant info with wrong variant', async () => { + // arrange: + const seqVarInvalid = new SeqvarImpl('grch37', '1', 123, 'A', 'T') + fetchMocker.mockResponse((req) => { + if (req.url.includes('alternative=G')) { + return Promise.resolve(JSON.stringify(seqvarCsqResponseBrca1)) + } + return Promise.reject('failed to fetch seqvar') + }) + + // act: + const client = new MehariClient() + // (with guard) + await expect(async () => await client.retrieveSeqvarsCsq(seqVarInvalid)).rejects.toThrow( + 'failed to fetch seqvar' + ) + + // assert: + }) +}) + +describe.concurrent('MehariClient/strucvar', () => { + beforeEach(() => { + fetchMocker.enableMocks() + fetchMocker.resetMocks() + }) + + it('fetches strucvar info correctly', async () => { + // arrange: + fetchMocker.mockResponseOnce(JSON.stringify(strucvarCsqResponseBrca1)) + + // act: + const client = new MehariClient() + const result = await client.retrieveStrucvarsCsq(strucvar) + + // assert: + expect(JSON.stringify(result)).toEqual( + JSON.stringify(StrucvarResult.fromJson(strucvarCsqResponseBrca1)) + ) + }) + + it('fails to fetch variant info with wrong variant', async () => { + // arrange: + const strucVarInvalid = new LinearStrucvarImpl('DUP', 'grch37', 'chr17', 43044295, 43044297) + fetchMocker.mockResponse((req) => { + if (req.url.includes('DEL')) { + return Promise.resolve(JSON.stringify(strucvarCsqResponseBrca1)) + } + return Promise.reject('failed to fetch strucvar') + }) + + // act: + const client = new MehariClient() + // (with guard) + await expect(async () => await client.retrieveStrucvarsCsq(strucVarInvalid)).rejects.toThrow( + 'failed to fetch strucvar' + ) + + // assert: + }) +}) diff --git a/src/api/mehari/client.ts b/src/api/mehari/client.ts new file mode 100644 index 0000000..5d95777 --- /dev/null +++ b/src/api/mehari/client.ts @@ -0,0 +1,65 @@ +import type { LinearStrucvar, Seqvar } from '../../lib/genomicVars' +import { SeqvarResult, StrucvarResult } from './types' + +/** API base URL to use. */ +const API_BASE_URL = '/internal/proxy/pubtator3-api' + +/** + * Client for Mehari API requests. + */ +export class MehariClient { + private apiBaseUrl: string + + constructor(apiBaseUrl?: string) { + this.apiBaseUrl = apiBaseUrl ?? API_BASE_URL + } + + /** + * Retrieve consequences for sequence variants. + * + * @param seqvar Sequence variant to retrieve consequences for. + * @param hgncId HGNC ID of gene to restrict results to. + * @returns The response from the API. + * @throws Error if the API request fails. + */ + async retrieveSeqvarsCsq(seqvar: Seqvar, hgncId?: string): Promise { + const { genomeBuild, chrom, pos, del, ins } = seqvar + const hgncSuffix = hgncId ? `&hgnc_id=${hgncId}` : '' + const url = + `${this.apiBaseUrl}seqvars/csq?genome_release=${genomeBuild}&` + + `chromosome=${chrom}&position=${pos}&reference=${del}&` + + `alternative=${ins}${hgncSuffix}` + + const response = await fetch(url, { + method: 'GET' + }) + if (!response.ok) { + throw new Error(`Failed to fetch sequence variant consequences: ${response.statusText}`) + } + const responseJson = await response.json() + return SeqvarResult.fromJson(responseJson) + } + + /** + * Retrieve consequences for structural variants. + * + * @param strucvar Structural variant to retrieve consequences for. + * @returns The response from the API. + * @throws Error if the API request fails. + */ + async retrieveStrucvarsCsq(strucvar: LinearStrucvar): Promise { + const { genomeBuild, chrom, start, stop, svType } = strucvar + const url = + `${this.apiBaseUrl}strucvars/csq?genome_release=${genomeBuild}&` + + `chromosome=${chrom}&start=${start}&stop=${stop}&sv_type=${svType}` + + const response = await fetch(url, { + method: 'GET' + }) + if (!response.ok) { + throw new Error(`Failed to fetch structural variant consequences: ${response.statusText}`) + } + const responseJson = await response.json() + return StrucvarResult.fromJson(responseJson) + } +} diff --git a/src/api/mehari/fixture.seqvarCsqResponse.BRCA1.json b/src/api/mehari/fixture.seqvarCsqResponse.BRCA1.json new file mode 100644 index 0000000..64d3a61 --- /dev/null +++ b/src/api/mehari/fixture.seqvarCsqResponse.BRCA1.json @@ -0,0 +1,98 @@ +{ + "version": { "tx_db": "0.5.0", "mehari": "0.7.0" }, + "query": { + "genome_release": "grch37", + "chromosome": "chr17", + "position": 41197751, + "reference": "G", + "alternative": "T", + "hgnc_id": null + }, + "result": [ + { + "consequences": ["MissenseVariant"], + "putative_impact": "Moderate", + "gene_symbol": "BRCA1", + "gene_id": "HGNC:1100", + "feature_type": { "SoTerm": { "term": "Transcript" } }, + "feature_id": "NM_007294.4", + "feature_biotype": "Coding", + "rank": { "ord": 23, "total": 23 }, + "hgvs_t": "c.5536C>A", + "hgvs_p": "p.Q1846K", + "tx_pos": { "ord": 5649, "total": 7088 }, + "cds_pos": { "ord": 5536, "total": 5592 }, + "protein_pos": { "ord": 1846, "total": 1864 }, + "distance": -1440, + "messages": null + }, + { + "consequences": ["MissenseVariant"], + "putative_impact": "Moderate", + "gene_symbol": "BRCA1", + "gene_id": "HGNC:1100", + "feature_type": { "SoTerm": { "term": "Transcript" } }, + "feature_id": "NM_007297.4", + "feature_biotype": "Coding", + "rank": { "ord": 22, "total": 22 }, + "hgvs_t": "c.5395C>A", + "hgvs_p": "p.Q1799K", + "tx_pos": { "ord": 5589, "total": 7028 }, + "cds_pos": { "ord": 5395, "total": 5451 }, + "protein_pos": { "ord": 1799, "total": 1817 }, + "distance": -1440, + "messages": null + }, + { + "consequences": ["MissenseVariant"], + "putative_impact": "Moderate", + "gene_symbol": "BRCA1", + "gene_id": "HGNC:1100", + "feature_type": { "SoTerm": { "term": "Transcript" } }, + "feature_id": "NM_007298.3", + "feature_biotype": "Coding", + "rank": { "ord": 22, "total": 22 }, + "hgvs_t": "c.2224C>A", + "hgvs_p": "p.Q742K", + "tx_pos": { "ord": 2243, "total": 3682 }, + "cds_pos": { "ord": 2224, "total": 2280 }, + "protein_pos": { "ord": 742, "total": 760 }, + "distance": -1440, + "messages": null + }, + { + "consequences": ["MissenseVariant"], + "putative_impact": "Moderate", + "gene_symbol": "BRCA1", + "gene_id": "HGNC:1100", + "feature_type": { "SoTerm": { "term": "Transcript" } }, + "feature_id": "NM_007300.4", + "feature_biotype": "Coding", + "rank": { "ord": 24, "total": 24 }, + "hgvs_t": "c.5599C>A", + "hgvs_p": "p.Q1867K", + "tx_pos": { "ord": 5712, "total": 7151 }, + "cds_pos": { "ord": 5599, "total": 5655 }, + "protein_pos": { "ord": 1867, "total": 1885 }, + "distance": -1440, + "messages": null + }, + { + "consequences": ["ThreePrimeUtrVariant"], + "putative_impact": "Modifier", + "gene_symbol": "BRCA1", + "gene_id": "HGNC:1100", + "feature_type": { "SoTerm": { "term": "Transcript" } }, + "feature_id": "NM_007299.4", + "feature_biotype": "Coding", + "rank": { "ord": 22, "total": 22 }, + "hgvs_t": "c.*50C>A", + "hgvs_p": "p.?", + "tx_pos": { "ord": 2257, "total": 3696 }, + "cds_pos": { "ord": 50, "total": 2100 }, + "protein_pos": null, + "distance": -1440, + "messages": null + } + ] +} diff --git a/src/api/mehari/fixture.strucvarCsqResponse.BRCA1.json b/src/api/mehari/fixture.strucvarCsqResponse.BRCA1.json new file mode 100644 index 0000000..a2e3a0f --- /dev/null +++ b/src/api/mehari/fixture.strucvarCsqResponse.BRCA1.json @@ -0,0 +1,45 @@ +{ + "version": { + "tx_db": "0.5.0", + "mehari": "0.8.0" + }, + "query": { + "genome_release": "grch37", + "chromosome": "chr17", + "start": 41176312, + "stop": 41277500, + "sv_type": "DEL" + }, + "result": [ + { + "hgnc_id": "HGNC:16919", + "transcript_effects": ["upstream_variant"] + }, + { + "hgnc_id": "HGNC:1100", + "transcript_effects": [ + "transcript_variant", + "exon_variant", + "splice_region_variant", + "intron_variant", + "upstream_variant", + "downstream_variant" + ] + }, + { + "hgnc_id": "HGNC:18315", + "transcript_effects": [ + "transcript_variant", + "exon_variant", + "splice_region_variant", + "intron_variant", + "upstream_variant", + "downstream_variant" + ] + }, + { + "hgnc_id": "HGNC:20691", + "transcript_effects": ["upstream_variant"] + } + ] +} diff --git a/src/api/mehari/index.ts b/src/api/mehari/index.ts new file mode 100644 index 0000000..886d07b --- /dev/null +++ b/src/api/mehari/index.ts @@ -0,0 +1,2 @@ +export * from './types' +export * from './client' diff --git a/src/api/mehari/types.ts b/src/api/mehari/types.ts new file mode 100644 index 0000000..ca436b9 --- /dev/null +++ b/src/api/mehari/types.ts @@ -0,0 +1,478 @@ +/** + * Types for interfacing with the Mehari API. + */ +import { GenomeBuild } from '@/lib/genomeBuilds' + +/** + * Enumeration for the putative impact. + */ +export enum SeqvarPutativeImpact { + High = 'HIGH', + Moderate = 'MODERATE', + Low = 'LOW', + Modifier = 'MODIFIER' +} + +/** + * Enumeration for the consequence. + */ +export enum SeqvarConsequence { + // high impact + ChromosomeNumberVariation = 'chromosome_number_variation', + ExonLossVariant = 'exon_loss_variant', + FrameshiftVariant = 'frameshift_variant', + RareAminoAcidVariant = 'rare_amino_acid_variant', + SpliceAcceptorVariant = 'splice_acceptor_variant', + SpliceDonorVariant = 'splice_donor_variant', + StartLost = 'start_lost', + StopGained = 'stop_gained', + StopLost = 'stop_lost', + TranscriptAblation = 'transcript_ablation', + // moderate impact + ThreePrimeUtrTruncation = '3_prime_UTR_truncation', + FivePrimeUtrTruncaction = '5_prime_UTR_truncation', + ConservativeInframeDeletion = 'conservative_inframe_deletion', + ConservativeInframeInsertion = 'conservative_inframe_insertion', + DisruptiveInframeDeletion = 'disruptive_inframe_deletion', + DisruptiveInframeInsertion = 'disruptive_inframe_insertion', + MissenseVariant = 'missense_variant', + RegulatoryRegionAblation = 'regulatory_region_ablation', + SpliceRegionVariant = 'splice_region_variant', + TbfsAblation = 'TFBS_ablation', + // low impact + FivePrimeUtrPrematureStartCodonGainVariant = '5_prime_UTR_premature_start_codon_gain_variant', + InitiatorCodonVariant = 'initiator_codon_variant', + StartRetained = 'start_retained', + StopRetainedVariant = 'stop_retained_variant', + SynonymousVariant = 'synonymous_variant', + // modifier + ThreePrimeUtrVariant = '3_prime_UTR_variant', + FivePrimeUtrVariant = '5_prime_UTR_variant', + CodingSequenceVariant = 'coding_sequence_variant', + ConservedIntergenicVariant = 'conserved_intergenic_variant', + ConservedIntronVariant = 'conserved_intron_variant', + DownstreamGeneVariant = 'downstream_gene_variant', + ExonVariant = 'exon_variant', + FeatureElongation = 'feature_elongation', + FeatureTruncation = 'feature_truncation', + GeneVariant = 'gene_variant', + IntergenicVariant = 'intergenic_variant', + IntronVariant = 'intron_variant', + MatureMirnaVariant = 'mature_miRNA_variant', + Mirna = 'miRNA', + NmdTranscriptVariant = 'NMD_transcript_variant', + NonCodingTranscriptExonVariant = 'non_coding_transcript_exon_variant', + NonCodingTranscriptIntronVariant = 'non_coding_transcript_intron_variant', + RegulatoryRegionAmplification = 'regulatory_region_amplification', + RegulatoryRegionVariant = 'regulatory_region_variant', + TfBindingSiteVariant = 'TF_binding_site_variant', + TfbsAmplification = 'TFBS_amplification', + TranscriptAmplification = 'transcript_amplification', + TranscriptVariant = 'transcript_variant', + UpstreamGeneVariant = 'upstream_gene_variant' +} + +/** + * Interface for one entry in the result as returned by API. + */ +export interface SeqvarResultEntry$Api { + /** The consequences of the allele. */ + consequences: SeqvarConsequence[] + /** The putative impact. */ + putative_impact: SeqvarPutativeImpact + /** The gene symbol. */ + gene_symbol: string + /** The gene identifier. */ + gene_id: string + /** The feature type. */ + feature_type: string + /** The feature identifier. */ + feature_id: string + /** The feature biotype. */ + feature_biotype: string + /** The feature tags. */ + feature_tag: string[] + /** The exon / intron rank. */ + rank?: number + /** HGVS c. notation. */ + hgvs_t?: string + /** HGVS p. notation. */ + hgvs_p?: string + /** cDNA position. */ + tx_pos?: number + /** CDS position. */ + cds_pos?: number + /** Protein position. */ + protein_pos?: number + /** Distance to feature. */ + distance?: number + /** Optional list of warnings and error messages. */ + messages?: string[] +} + +/** + * Interface for one entry in the result. + */ +export interface SeqvarResultEntry { + /** The consequences of the allele. */ + consequences: SeqvarConsequence[] + /** The putative impact. */ + putativeImpact: SeqvarPutativeImpact + /** The gene symbol. */ + geneSymbol: string + /** The gene identifier. */ + geneId: string + /** The feature type. */ + featureType: string + /** The feature identifier. */ + featureId: string + /** The feature biotype. */ + featureBiotype: string + /** The feature tags. */ + featureTag: string[] + /** The exon / intron rank. */ + rank?: number + /** HGVS c. notation. */ + hgvsT?: string + /** HGVS p. notation. */ + hgvsP?: string + /** cDNA position. */ + txPos?: number + /** CDS position. */ + cdsPos?: number + /** Protein position. */ + proteinPos?: number + /** Distance to feature. */ + distance?: number + /** Optional list of warnings and error messages. */ + messages?: string[] +} + +/** + * Helper class for converting `ResultEntry$Api` to `ResultEntry`. + */ +class SeqvarResultEntry$Type { + /** Convert `SeqvarResultEntry$Api` to `SeqvarResultEntry`. */ + fromJson(json: SeqvarResultEntry$Api): SeqvarResultEntry { + return { + consequences: json.consequences, + putativeImpact: json.putative_impact, + geneSymbol: json.gene_symbol, + geneId: json.gene_id, + featureType: json.feature_type, + featureId: json.feature_id, + featureBiotype: json.feature_biotype, + featureTag: json.feature_tag, + rank: json.rank, + hgvsT: json.hgvs_t, + hgvsP: json.hgvs_p, + txPos: json.tx_pos, + cdsPos: json.cds_pos, + proteinPos: json.protein_pos, + distance: json.distance, + messages: json.messages + } + } +} + +/** + * Helper for converting `SeqvarResultEntry$Api` to `SeqvarResultEntry`. + */ +export const SeqvarResultEntry: SeqvarResultEntry$Type = new SeqvarResultEntry$Type() + +/** + * Interface for the version as returned by API. + */ +export interface Version$Api { + /** Transcript database version. */ + tx_db: string + /** The application version. */ + mehari: string +} + +/** + * Interface for the version. + */ +export interface SeqvarVersion { + /** Transcript database version. */ + txDb: string + /** The application version. */ + mehari: string +} + +/** + * Helper class for converting `Version$Api` to `Version`. + */ +class Version$Type { + /** Convert `Version$Api` to `Version`. */ + fromJson(json: Version$Api): SeqvarVersion { + return { + txDb: json.tx_db, + mehari: json.mehari + } + } +} + +/** + * Helper for converting `Version$Api` to `Version`. + */ +export const Version: Version$Type = new Version$Type() + +/** + * Interface for the query as returned by API. + */ +export interface SeqvarQuery$Api { + /** The assembly. */ + genome_release: string + /** SPDI sequence. */ + chromosome: string + /** SPDI position. */ + position: number + /** SPDI deletion. */ + reference: string + /** SPDI insertion. */ + alternative: string + /** Optionally, the HGNC ID of the gene to limit to. */ + hgnc_id?: string +} + +/** + * Interface for the query. + */ +export interface SeqvarQuery { + /** The assembly. */ + genomeRelease: string + /** SPDI sequence. */ + chromosome: string + /** SPDI position. */ + position: number + /** SPDI deletion. */ + reference: string + /** SPDI insertion. */ + alternative: string + /** Optionally, the HGNC ID of the gene to limit to. */ + hgncId?: string +} + +/** + * Helper class for converting `SeqvarQuery$Api` to `SeqvarQuery`. + */ +class SeqvarQuery$Type { + /** Convert `SeqvarQuery$Api` to `SeqvarQuery`. */ + fromJson(json: SeqvarQuery$Api): SeqvarQuery { + return { + genomeRelease: json.genome_release, + chromosome: json.chromosome, + position: json.position, + reference: json.reference, + alternative: json.alternative, + hgncId: json.hgnc_id + } + } +} + +/** + * Helper for converting `SeqvarQuery$Api` to `SeqvarQuery`. + */ +export const SeqvarQuery: SeqvarQuery$Type = new SeqvarQuery$Type() + +/** + * Interface for the result as returned by API. + */ +export interface SeqvarResult$Api { + /** Version information. */ + version: Version$Api + /** The original query records. */ + query: SeqvarQuery$Api + /** The resulting records for the scored genes. */ + result: SeqvarResultEntry$Api[] +} + +/** + * Interface for the result. + */ +export interface SeqvarResult { + /** Version information. */ + version: SeqvarVersion + /** The original query records. */ + query: SeqvarQuery + /** The resulting records for the scored genes. */ + result: SeqvarResultEntry[] +} + +/** + * Helper class for converting `Result$Api` to `Result`. + */ +class SeqvarResult$Type { + /** Convert `Result$Api` to `Result`. */ + fromJson(json: SeqvarResult$Api): SeqvarResult { + return { + version: Version.fromJson(json.version), + query: SeqvarQuery.fromJson(json.query), + result: json.result.map((entry) => SeqvarResultEntry.fromJson(entry)) + } + } +} + +/** + * Helper for converting `SeqvarResult$Api` to `SeqvarResult`. + */ +export const SeqvarResult: SeqvarResult$Type = new SeqvarResult$Type() + +/** + * Enumeration for the effect on transcript. + */ +export enum TranscriptEffect { + TranscriptVariant = 'transcript_variant', + ExonVariant = 'exon_variant', + SpliceRegionVariant = 'splice_region_variant', + IntronVariant = 'intron_variant', + UpstreamVariant = 'upstream_variant', + DownstreamVariant = 'downstream_variant', + IntergenicVariant = 'intergenic_variant' +} + +/** + * Interface for one entry in the result as returned by API. + */ +export interface GeneTranscriptEffects$Api { + /** HGNC identifier */ + hgnc_id: string + /** Transcript effects for the gene. */ + transcript_effects: TranscriptEffect[] +} + +/** + * Explanation of transcript effect per individual gene. + */ +export interface GeneTranscriptEffects { + /** HGNC identifier */ + hgncId: string + /** Transcript effects for the gene. */ + transcriptEffects: TranscriptEffect[] +} + +/** + * Helper class for converting `GeneTranscriptEffects$Api` to `GeneTranscriptEffects`. + */ +class GeneTranscriptEffects$Type { + /** Convert `GeneTranscriptEffects$Api` to `GeneTranscriptEffects`. */ + fromJson(json: GeneTranscriptEffects$Api): GeneTranscriptEffects { + return { + hgncId: json.hgnc_id, + transcriptEffects: json.transcript_effects + } + } +} + +/** + * Helper for converting `GeneTranscriptEffects$Api` to `GeneTranscriptEffects`. + */ +export const GeneTranscriptEffects: GeneTranscriptEffects$Type = new GeneTranscriptEffects$Type() + +/** + * Enumeration for the structural variant type. + */ +export enum StrucVarType { + Del = 'del', + Dup = 'dup', + Ins = 'ins', + Bnd = 'bnd', + Inv = 'inv' +} + +/** + * Parameters for `/strucvars/csq` as returned by the API. + */ +export interface StrucvarsQuery$Api { + /** The assembly. */ + genome_release: GenomeBuild + /** Chromosome sequence. */ + chromosome: string + /** Start position. */ + start: number + /** 1-based stop position. */ + stop: number + /** Type of SV. */ + sv_type: StrucVarType +} + +/** + * Parameters for `/strucvars/csq`. + */ +export interface StrucvarsQuery { + /** The assembly. */ + genomeRelease: GenomeBuild + /** Chromosome sequence. */ + chromosome: string + /** Start position. */ + start: number + /** 1-based stop position. */ + stop: number + /** Type of SV. */ + svType: StrucVarType +} + +/** + * Helper class for converting `StrucvarsQuery$Api` to `StrucvarsQuery`. + */ +class StrucvarsQuery$Type { + /** Convert `StrucvarsQuery$Api` to `StrucvarsQuery`. */ + fromJson(json: StrucvarsQuery$Api): StrucvarsQuery { + return { + genomeRelease: json.genome_release, + chromosome: json.chromosome, + start: json.start, + stop: json.stop, + svType: json.sv_type + } + } +} + +/** + * Helper for converting `StrucvarsQuery$Api` to `StrucvarsQuery`. + */ +export const StrucvarsQuery: StrucvarsQuery$Type = new StrucvarsQuery$Type() + +/** + * Interface for the result as returned by API. + */ +export interface StrucvarResult$Api { + /** Version information. */ + version: Version$Api + /** The original query records. */ + query: SeqvarQuery$Api + /** The resulting records for the scored genes. */ + result: SeqvarResultEntry$Api[] +} + +/** + * Interface for the result. + */ +export interface StrucvarResult { + /** Version information. */ + version: SeqvarVersion + /** The original query records. */ + query: SeqvarQuery + /** The resulting records for the scored genes. */ + result: SeqvarResultEntry[] +} + +/** + * Helper class for converting `Result$Api` to `Result`. + */ +class StrucvarResult$Type { + /** Convert `Result$Api` to `Result`. */ + fromJson(json: StrucvarResult$Api): StrucvarResult { + return { + version: Version.fromJson(json.version), + query: SeqvarQuery.fromJson(json.query), + result: json.result.map((entry) => SeqvarResultEntry.fromJson(entry)) + } + } +} + +/** + * Helper for converting `StrucvarResult$Api` to `StrucvarResult`. + */ +export const StrucvarResult: StrucvarResult$Type = new StrucvarResult$Type() diff --git a/src/lib/genomicVars.ts b/src/lib/genomicVars.ts index 5fb158d..2d65198 100644 --- a/src/lib/genomicVars.ts +++ b/src/lib/genomicVars.ts @@ -117,6 +117,54 @@ export interface Seqvar { userRepr: string } +/** + * Implementation of the `Seqvar` interface. + */ +export class SeqvarImpl implements Seqvar { + genomeBuild: GenomeBuild + chrom: string + pos: number + del: string + ins: string + userRepr: string + + constructor( + genomeBuild: GenomeBuild, + chrom: string, + pos: number, + del: string, + ins: string, + userRepr?: string + ) { + this.genomeBuild = genomeBuild + this.chrom = chrom + this.pos = pos + this.del = del + this.ins = ins + this.userRepr = + userRepr ?? `${this.genomeBuild}-${this.chrom}-${this.pos}-${this.del}-${this.ins}` + } + + /** Return the "object name" to be used in the API to the backend etc. */ + toName(): string { + return `${this.genomeBuild}-${this.chrom}-${this.pos}-${this.del}-${this.ins}` + } +} + +/** + * Construct a `SeqvarImpl` from a `Seqvar`. + */ +export function seqvarImplFromSeqvar(variant: Seqvar): SeqvarImpl { + return new SeqvarImpl( + variant.genomeBuild, + variant.chrom, + variant.pos, + variant.del, + variant.ins, + variant.userRepr + ) +} + /** Base class for exceptions when parsing variants. */ export class InvalidVariant extends Error { constructor(message: string) { @@ -295,6 +343,58 @@ export interface LinearStrucvar { userRepr: string } +/** + * Implementation of the `LinearStrucvar` interface. + */ +export class LinearStrucvarImpl implements LinearStrucvar { + svType: 'DEL' | 'DUP' + genomeBuild: GenomeBuild + chrom: string + start: number + stop: number + copyNumber?: number + userRepr: string + + constructor( + svType: 'DEL' | 'DUP', + genomeBuild: GenomeBuild, + chrom: string, + start: number, + stop: number, + copyNumber?: number, + userRepr?: string + ) { + this.svType = svType + this.genomeBuild = genomeBuild + this.chrom = chrom + this.start = start + this.stop = stop + this.copyNumber = copyNumber + this.userRepr = + userRepr ?? `${this.svType}-${this.genomeBuild}-${this.chrom}-${this.start}-${this.stop}` + } + + /** Return the "object name" to be used in the API to the backend etc. */ + toName(): string { + return `${this.svType}-${this.genomeBuild}-${this.chrom}-${this.start}-${this.stop}` + } +} + +/** + * Construct a `LinearStrucvarImpl` from a `LinearStrucvar`. + */ +export function linearStrucvarImplFromLinearStrucvar(variant: LinearStrucvar): LinearStrucvarImpl { + return new LinearStrucvarImpl( + variant.svType, + variant.genomeBuild, + variant.chrom, + variant.start, + variant.stop, + variant.copyNumber, + variant.userRepr + ) +} + /** All supported structural variant types. */ export type Strucvar = LinearStrucvar // | ...