@sjcrh/proteinpaint-server 2.38.1 → 2.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.38.1",
3
+ "version": "2.39.1",
4
4
  "description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
5
5
  "main": "server.js",
6
6
  "bin": "start.js",
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@sjcrh/augen": "2.35.0",
60
- "@sjcrh/proteinpaint-rust": "2.38.1",
60
+ "@sjcrh/proteinpaint-rust": "2.39.0",
61
61
  "better-sqlite3": "^7.5.3",
62
62
  "body-parser": "^1.15.2",
63
63
  "canvas": "~2.9.3",
package/routes/gdc.maf.ts CHANGED
@@ -3,15 +3,28 @@ import path from 'path'
3
3
  import got from 'got'
4
4
  import serverconfig from '#src/serverconfig.js'
5
5
 
6
- const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov'
7
- const maxFileNumber = 1000
6
+ /*
7
+ this route lists available gdc MAF files based on user's cohort filter
8
+ and return them to client to be shown in a table for selection
9
+ */
10
+
11
+ const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov' // to switch to serverconfig export
12
+
13
+ const maxFileNumber = 1000 // determines max number of files to return to client
14
+ // preliminary testing:
15
+ // 36s for 1000 (87Mb)
16
+ // 78s for 2000 (177Mb)
17
+ // if safe to increase to 2000, maybe fast when this runs in gdc env
18
+
8
19
  const allowedWorkflowType = 'Aliquot Ensemble Somatic Variant Merging and Masking'
9
- const maxTotalSizeCompressed = serverconfig.features.gdcMafMaxFileSize || 50000000 // 50Mb
20
+
21
+ // change to 400 so it won't limit number of files; should keep this setting as a safeguard; also it's fast to check file size (.5s in gdc.mafBuild.ts)
22
+ export const maxTotalSizeCompressed = serverconfig.features.gdcMafMaxFileSize || 400000000 // 400Mb
10
23
 
11
24
  export const api = {
12
25
  endpoint: 'gdc/maf',
13
26
  methods: {
14
- get: {
27
+ all: {
15
28
  init,
16
29
  request: {
17
30
  typeId: 'GdcMafRequest'
@@ -5,9 +5,9 @@ import { run_rust_stream } from '@sjcrh/proteinpaint-rust'
5
5
  import serverconfig from '#src/serverconfig.js'
6
6
  import Readable from 'stream'
7
7
  import { GdcMafBuildRequest } from '#shared/types/routes/gdc.mafBuild.ts'
8
+ import { maxTotalSizeCompressed } from './gdc.maf.ts'
8
9
 
9
10
  const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov'
10
- const maxTotalSizeCompressed = serverconfig.features.gdcMafMaxFileSize || 50000000 // 50Mb
11
11
 
12
12
  export const api = {
13
13
  endpoint: 'gdc/mafBuild',
@@ -43,10 +43,16 @@ async function buildMaf(q: GdcMafBuildRequest, res: any) {
43
43
  const t0 = Date.now()
44
44
 
45
45
  const fileLst2 = (await getFileLstUnderSizeLimit(q.fileIdLst)) as string[]
46
- console.log('test gdc maf sizes', Date.now() - t0)
46
+
47
+ if (serverconfig.debugmode)
48
+ console.log(
49
+ `${fileLst2.length} out of ${q.fileIdLst.length} input MAF files accepted by size limit`,
50
+ Date.now() - t0
51
+ )
47
52
 
48
53
  const arg = {
49
54
  fileIdLst: fileLst2,
55
+ columns: q.columns,
50
56
  host: path.join(apihost, 'data') // must use the /data/ endpoint from current host
51
57
  }
52
58
 
@@ -55,11 +61,12 @@ async function buildMaf(q: GdcMafBuildRequest, res: any) {
55
61
  res.setHeader('Content-Disposition', 'attachment; filename=cohort.maf.gz')
56
62
  rustStream.pipe(res)
57
63
 
58
- console.log('rust gdcmaf', Date.now() - t0)
59
-
60
64
  rustStream.on('end', () => {
65
+ // report amount of time taken to run rust
66
+ if (serverconfig.debugmode) console.log('rust gdcmaf', Date.now() - t0)
61
67
  res.end()
62
68
  })
69
+
63
70
  rustStream.on('error', err => {
64
71
  console.error(err)
65
72
  res.statusCode = 500
@@ -80,7 +87,7 @@ async function getFileLstUnderSizeLimit(lst: string[]) {
80
87
  op: 'in',
81
88
  content: { field: 'file_id', value: lst }
82
89
  },
83
- size: 1000,
90
+ size: 10000,
84
91
  fields: 'file_size'
85
92
  }
86
93
  const headers = { 'Content-Type': 'application/json', Accept: 'application/json' }
package/routes/hicdata.ts CHANGED
@@ -40,7 +40,7 @@ export const api: any = {
40
40
  }
41
41
  }
42
42
 
43
- function init({ genomes }) {
43
+ function init() {
44
44
  return async (req: any, res: any): Promise<void> => {
45
45
  try {
46
46
  const payload = await handle_hicdata(req.query as HicdataRequest)
@@ -60,7 +60,7 @@ function handle_hicdata(q: HicdataRequest) {
60
60
  if (e) reject({ error: 'illegal file name' })
61
61
 
62
62
  const par = [
63
- // TODO add option for observed/oe
63
+ q.matrixType || 'observed',
64
64
  q.nmeth || 'NONE',
65
65
  file,
66
66
  q.pos1,
@@ -1,4 +1,4 @@
1
- // import { getcategoriesRequest, getcategoriesResponse } from '#shared/types/routes/termdb.categories'
1
+ import { getcategoriesRequest, getcategoriesResponse } from '#shared/types/routes/termdb.categories.ts'
2
2
  import { getOrderedLabels } from '#src/termdb.barchart.js'
3
3
  import { getData } from '#src/termdb.matrix.js'
4
4
 
@@ -68,7 +68,7 @@ export const api: any = {
68
68
 
69
69
  function init({ genomes }) {
70
70
  return async (req: any, res: any): Promise<void> => {
71
- const q = req.query // as getcategoriesRequest
71
+ const q = req.query as getcategoriesRequest
72
72
  try {
73
73
  const g = genomes[req.query.genome]
74
74
  if (!g) throw 'invalid genome name'
@@ -88,8 +88,8 @@ function init({ genomes }) {
88
88
  }
89
89
 
90
90
  async function trigger_getcategories(
91
- q: { tid: string | number; type: string; filter: any; term1_q: any; currentGeneNames: any },
92
- res: { send: (arg0: { lst: any[]; orderedLabels: any }) => void },
91
+ q: { tid: string | number; type: string; filter: any; term1_q: any; currentGeneNames?: string[]; rglst?: any },
92
+ res: { send: (arg0: { lst: any[]; orderedLabels?: any }) => void },
93
93
  tdb: { q: { termjsonByOneid: (arg0: any) => any } },
94
94
  ds: { assayAvailability: { byDt: { [s: string]: any } | ArrayLike<any> } },
95
95
  genome: any
@@ -99,13 +99,15 @@ async function trigger_getcategories(
99
99
  if (!q.tid) throw '.tid missing'
100
100
  const term =
101
101
  q.type == 'geneVariant' ? { name: q.tid, type: 'geneVariant', isleaf: true } : tdb.q.termjsonByOneid(q.tid)
102
+
102
103
  const arg = {
103
104
  filter: q.filter,
104
105
  terms:
105
106
  q.type == 'geneVariant'
106
107
  ? [{ term: term, q: { isAtomic: true } }]
107
108
  : [{ id: q.tid, term, q: q.term1_q || getDefaultQ(term, q) }],
108
- currentGeneNames: q.currentGeneNames
109
+ currentGeneNames: q.currentGeneNames, // optional, from mds3 mayAddGetCategoryArgs()
110
+ rglst: q.rglst // optional, from mds3 mayAddGetCategoryArgs()
109
111
  }
110
112
 
111
113
  const data = await getData(arg, ds, genome)
@@ -200,7 +202,7 @@ async function trigger_getcategories(
200
202
  res.send({
201
203
  lst,
202
204
  orderedLabels
203
- })
205
+ } as getcategoriesResponse)
204
206
  }
205
207
 
206
208
  function getDefaultQ(
@@ -0,0 +1,176 @@
1
+ import { TermdbClusterRequest, TermdbClusterResponse } from '#shared/types/routes/termdb.cluster.ts'
2
+ import path from 'path'
3
+ import * as utils from '#src/utils.js'
4
+ import serverconfig from '#src/serverconfig.js'
5
+ import { GeneExpressionQuery, GeneExpressionQueryNative } from '#shared/types/dataset.ts'
6
+ import { gdc_validate_query_geneExpression } from '#src/mds3.gdc.js'
7
+ import { mayLimitSamples } from '#src/mds3.filter.js'
8
+ import { doClustering } from '#src/doClustering.js' // unable to convert this to ts yet, when converted, move all code here
9
+ import { dtgeneexpression } from '#shared/common.js'
10
+
11
+ export const api = {
12
+ endpoint: 'termdb/cluster',
13
+ methods: {
14
+ all: {
15
+ init,
16
+ request: {
17
+ typeId: 'TermdbClusterRequest'
18
+ },
19
+ response: {
20
+ typeId: 'TermdbClusterResponse'
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ function init({ genomes }) {
27
+ return async (req: any, res: any): Promise<void> => {
28
+ const q = req.query as TermdbClusterRequest
29
+ let result
30
+ try {
31
+ const g = genomes[q.genome]
32
+ if (!g) throw 'invalid genome name'
33
+ const ds = g.datasets[q.dslabel]
34
+ if (!ds) throw 'invalid dataset name'
35
+ if (ds.__gdc && !ds.__gdc.doneCaching)
36
+ throw 'The server has not finished caching the case IDs: try again in ~2 minutes'
37
+ if (q.dataType == dtgeneexpression) {
38
+ if (!ds.queries?.geneExpression) throw 'no geneExpression data on this dataset'
39
+ result = (await getResult(q, ds)) as TermdbClusterResponse
40
+ } else {
41
+ throw 'unknown q.dataType ' + q.dataType
42
+ }
43
+ } catch (e: any) {
44
+ if (e.stack) console.log(e.stack)
45
+ result = {
46
+ status: e.status || 400,
47
+ error: e.message || e
48
+ } as TermdbClusterResponse
49
+ }
50
+ res.send(result)
51
+ }
52
+ }
53
+
54
+ async function getResult(q: TermdbClusterRequest, ds: any) {
55
+ const { gene2sample2value, byTermId, bySampleId } = await ds.queries.geneExpression.get(q)
56
+ if (gene2sample2value.size == 0) throw 'no data'
57
+ if (gene2sample2value.size == 1) {
58
+ // get data for only 1 gene; still return data, may create violin plot later
59
+ const g = Array.from(gene2sample2value.keys())[0]
60
+ return { gene: g, data: gene2sample2value.get(g) }
61
+ }
62
+
63
+ // have data for multiple genes, run clustering
64
+ const t = Date.now() // use "t=new Date()" will lead to tsc error
65
+ const clustering = await doClustering(gene2sample2value, q, ds)
66
+ if (serverconfig.debugmode) console.log('clustering done:', Date.now() - t, 'ms')
67
+ return { clustering, byTermId, bySampleId }
68
+ }
69
+
70
+ export async function validate_query_geneExpression(ds: any, genome: any) {
71
+ const q = ds.queries.geneExpression as GeneExpressionQuery
72
+ if (!q) return
73
+
74
+ if (q.src == 'gdcapi') {
75
+ gdc_validate_query_geneExpression(ds, genome)
76
+ // q.get() added
77
+ return
78
+ }
79
+ if (q.src == 'native') {
80
+ validateNative(q, ds, genome)
81
+ return
82
+ }
83
+ throw 'unknown queries.geneExpression.src'
84
+ }
85
+
86
+ async function validateNative(q: GeneExpressionQueryNative, ds: any, genome: any) {
87
+ q.file = path.join(serverconfig.tpmasterdir, q.file)
88
+ await utils.validate_tabixfile(q.file)
89
+ q.nochr = await utils.tabix_is_nochr(q.file, null, genome)
90
+ q.samples = [] as number[]
91
+
92
+ {
93
+ // is a gene-by-sample matrix file
94
+ const lines = await utils.get_header_tabix(q.file)
95
+ if (!lines[0]) throw 'header line missing from ' + q.file
96
+ const l = lines[0].split('\t')
97
+ if (l.slice(0, 4).join('\t') != '#chr\tstart\tstop\tgene') throw 'header line has wrong content for columns 1-4'
98
+ for (let i = 4; i < l.length; i++) {
99
+ const id = ds.cohort.termdb.q.sampleName2id(l[i])
100
+ if (id == undefined) throw 'unknown sample from header'
101
+ q.samples.push(id)
102
+ }
103
+ console.log(q.samples.length, 'samples from geneExpression of', ds.label)
104
+ }
105
+
106
+ /*
107
+ query exp data one gene at a time
108
+ param{}
109
+ .genes[{}]
110
+ .gene=str
111
+ .chr=str
112
+ .start=int
113
+ .stop=int
114
+ .filterObj{}
115
+ */
116
+ q.get = async (param: TermdbClusterRequest) => {
117
+ const limitSamples = await mayLimitSamples(param, q.samples, ds)
118
+ if (limitSamples?.size == 0) {
119
+ // got 0 sample after filtering, must still return expected structure with no data
120
+ return { gene2sample2value: new Set(), byTermId: {}, bySampleId: {} }
121
+ }
122
+
123
+ // has at least 1 sample passing filter and with exp data
124
+ // TODO what if there's just 1 sample not enough for clustering?
125
+ const bySampleId = {}
126
+ if (limitSamples) {
127
+ for (const sid of limitSamples) {
128
+ bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) }
129
+ }
130
+ } else {
131
+ // use all samples with exp data
132
+ for (const sid of q.samples) {
133
+ bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) }
134
+ }
135
+ }
136
+
137
+ const gene2sample2value = new Map() // k: gene symbol, v: { sampleId : value }
138
+
139
+ for (const g of param.genes) {
140
+ // FIXME newly added geneVariant terms from client to be changed to {gene} but not {name}
141
+ if (!g.gene) continue
142
+
143
+ if (!g.chr) {
144
+ // quick fix: newly added gene from client will lack chr/start/stop
145
+ const lst = genome.genedb.getjsonbyname.all(g.gene)
146
+ if (lst.length == 0) continue
147
+ const j = JSON.parse(lst.find(i => i.isdefault).genemodel || lst[0].genemodel)
148
+ g.start = j.start
149
+ g.stop = j.stop
150
+ g.chr = j.chr
151
+ }
152
+
153
+ gene2sample2value.set(g.gene, {})
154
+ await utils.get_lines_bigfile({
155
+ args: [q.file, (q.nochr ? g.chr?.replace('chr', '') : g.chr) + ':' + g.start + '-' + g.stop], // must do g.chr?.replace to avoid tsc error
156
+ callback: line => {
157
+ const l = line.split('\t')
158
+ // case-insensitive match! FIXME if g.gene is alias won't work
159
+ if (l[3].toLowerCase() != g.gene.toLowerCase()) return
160
+ for (let i = 4; i < l.length; i++) {
161
+ const sampleId = q.samples[i - 4]
162
+ if (limitSamples && !limitSamples.has(sampleId)) continue // doing filtering and sample of current column is not used
163
+ // if l[i] is blank string?
164
+ const v = Number(l[i])
165
+ if (Number.isNaN(v)) throw 'exp value not number'
166
+ gene2sample2value.get(g.gene)[sampleId] = v
167
+ }
168
+ }
169
+ } as any)
170
+ // Above!! add "as any" to suppress a npx tsc alert
171
+ }
172
+ // pass blank byTermId to match with expected output structure
173
+ const byTermId = {}
174
+ return { gene2sample2value, byTermId, bySampleId }
175
+ }
176
+ }
@@ -1,7 +1,6 @@
1
- // import { getdescrstatsRequest, getdescrstatsResponse } from '#shared/types/routes/termdb.descrstats'
2
- import roundValue from '#shared/roundValue.js'
3
- import computePercentile from '#shared/compute.percentile.js'
4
- import * as termdbsql from '#src/termdb.sql.js'
1
+ import { getdescrstatsRequest, getdescrstatsResponse } from '#shared/types/routes/termdb.getdescrstats.ts'
2
+ import * as termdbsql from '../src/termdb.sql.js'
3
+ import Summarystats from '../shared/descriptive.stats.js'
5
4
 
6
5
  export const api: any = {
7
6
  endpoint: 'termdb/descrstats',
@@ -62,7 +61,7 @@ export const api: any = {
62
61
 
63
62
  function init({ genomes }) {
64
63
  return async (req: any, res: any): Promise<void> => {
65
- const q = req.query // as getdescrstatsRequest
64
+ const q = req.query as getdescrstatsRequest
66
65
  try {
67
66
  const g = genomes[req.query.genome]
68
67
  if (!g) throw 'invalid genome name'
@@ -104,42 +103,5 @@ async function trigger_getdescrstats(q: any, res: any, ds: any) {
104
103
  }
105
104
  values.push(value)
106
105
  }
107
-
108
- // compute statistics
109
- // total
110
- const total = values.length
111
-
112
- // mean
113
- const sum = values.reduce((a, b) => a + b, 0)
114
- const mean = sum / total
115
-
116
- // percentiles
117
- const p25 = computePercentile(values, 25)
118
- const median = computePercentile(values, 50)
119
- const p75 = computePercentile(values, 75)
120
-
121
- // standard deviation
122
- // get sum of squared differences from mean
123
- const sumSqDiff = values.map(v => (v - mean) ** 2).reduce((a, b) => a + b, 0)
124
- // get variance
125
- const variance = sumSqDiff / (values.length - 1)
126
- // get standard deviation
127
- const sd = Math.sqrt(variance)
128
-
129
- // min/max
130
- const min = Math.min(...values)
131
- const max = Math.max(...values)
132
-
133
- res.send({
134
- values: [
135
- { id: 'total', label: 'n', value: total },
136
- { id: 'min', label: 'Minimum', value: roundValue(min, 2) },
137
- { id: 'p25', label: '1st quartile', value: roundValue(p25, 2) },
138
- { id: 'median', label: 'Median', value: roundValue(median, 2) },
139
- { id: 'mean', label: 'Mean', value: roundValue(mean, 2) },
140
- { id: 'p75', label: '3rd quartile', value: roundValue(p75, 2) },
141
- { id: 'max', label: 'Maximum', value: roundValue(max, 2) },
142
- { id: 'sd', label: 'Standard deviation', value: roundValue(sd, 2) }
143
- ]
144
- })
106
+ res.send(Summarystats(values) as getdescrstatsResponse)
145
107
  }
@@ -0,0 +1,99 @@
1
+ import {
2
+ getnumericcategoriesRequest,
3
+ getnumericcategoriesResponse
4
+ } from '#shared/types/routes/termdb.getnumericcategories.ts'
5
+ import * as termdbsql from '#src/termdb.sql.js'
6
+
7
+ export const api: any = {
8
+ endpoint: 'termdb/numericcategories',
9
+ methods: {
10
+ get: {
11
+ init,
12
+ request: {
13
+ typeId: 'getnumericcategoriesRequest'
14
+ },
15
+ response: {
16
+ typeId: 'getnumericcategoriesResponse'
17
+ },
18
+ examples: [
19
+ {
20
+ request: {
21
+ body: {
22
+ genome: 'hg38-test',
23
+ dslabel: 'TermdbTest',
24
+ embedder: 'localhost',
25
+ tid: 'aaclassic_5',
26
+ filter: {
27
+ type: 'tvslst',
28
+ in: true,
29
+ join: '',
30
+ lst: [
31
+ {
32
+ tag: 'cohortFilter',
33
+ type: 'tvs',
34
+ tvs: {
35
+ term: {
36
+ name: 'Cohort',
37
+ type: 'categorical',
38
+ values: { ABC: { label: 'ABC' }, XYZ: { label: 'XYZ' } },
39
+ id: 'subcohort',
40
+ isleaf: false,
41
+ groupsetting: { disabled: true }
42
+ },
43
+ values: [{ key: 'ABC', label: 'ABC' }]
44
+ }
45
+ }
46
+ ]
47
+ }
48
+ }
49
+ },
50
+ response: {
51
+ header: { status: 200 }
52
+ }
53
+ }
54
+ ]
55
+ },
56
+ post: {
57
+ alternativeFor: 'get',
58
+ init
59
+ }
60
+ }
61
+ }
62
+
63
+ function init({ genomes }) {
64
+ return async (req: any, res: any): Promise<void> => {
65
+ const q = req.query as getnumericcategoriesRequest
66
+ try {
67
+ const g = genomes[req.query.genome]
68
+ if (!g) throw 'invalid genome name'
69
+ const ds = g.datasets[req.query.dslabel]
70
+ if (!ds) throw 'invalid dataset name'
71
+ const tdb = ds.cohort.termdb
72
+ if (!tdb) throw 'invalid termdb object'
73
+
74
+ await trigger_getnumericcategories(q, res, tdb, ds) // as getnumericcategoriesResponse
75
+ } catch (e) {
76
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
77
+ // @ts-ignore
78
+ res.send({ error: e?.message || e })
79
+ if (e instanceof Error && e.stack) console.log(e)
80
+ }
81
+ }
82
+ }
83
+
84
+ async function trigger_getnumericcategories(
85
+ q: { tid: any; filter?: any },
86
+ res: { send: (arg0: { lst: any }) => void },
87
+ tdb: { q: { termjsonByOneid: (arg0: any) => any } },
88
+ ds: any
89
+ ) {
90
+ if (!q.tid) throw '.tid missing'
91
+ const term = tdb.q.termjsonByOneid(q.tid)
92
+ const arg = {
93
+ ds,
94
+ term_id: q.tid,
95
+ filter: q.filter
96
+ }
97
+ const lst = await termdbsql.get_summary_numericcategories(arg)
98
+ res.send({ lst } as getnumericcategoriesResponse)
99
+ }
@@ -1,6 +1,7 @@
1
- // import { getpercentileRequest, getpercentileResponse } from '#shared/types/routes/termdb.getpercentile'
2
- import * as termdbsql from '#src/termdb.sql.js'
3
- import computePercentile from '#shared/compute.percentile.js'
1
+ import { getpercentileRequest, getpercentileResponse } from '#shared/types/routes/termdb.getpercentile.ts'
2
+ import * as termdbsql from '../src/termdb.sql.js'
3
+ import computePercentile from '../shared/compute.percentile.js'
4
+ import { Filter } from '../shared/types/filter'
4
5
 
5
6
  export const api: any = {
6
7
  endpoint: 'termdb/getpercentile',
@@ -61,7 +62,7 @@ export const api: any = {
61
62
 
62
63
  function init({ genomes }) {
63
64
  return async (req: any, res: any): Promise<void> => {
64
- const q = req.query // as getpercentileRequest
65
+ const q = req.query as getpercentileRequest
65
66
  try {
66
67
  const g = genomes[req.query.genome]
67
68
  if (!g) throw 'invalid genome name'
@@ -78,7 +79,7 @@ function init({ genomes }) {
78
79
  }
79
80
 
80
81
  async function trigger_getpercentile(
81
- q: { tid: string; getpercentile: number[]; filter: string },
82
+ q: { tid: string; getpercentile: number[]; filter: Filter },
82
83
  res: { send: (arg0: { values: number[] }) => void },
83
84
  ds: { cohort: { termdb: { q: { termjsonByOneid: (arg0: any) => any } } } }
84
85
  ) {
@@ -113,5 +114,5 @@ async function trigger_getpercentile(
113
114
  const perc_value = computePercentile(values, percentile)
114
115
  perc_values.push(perc_value)
115
116
  }
116
- res.send({ values: perc_values })
117
+ res.send({ values: perc_values } as getpercentileResponse)
117
118
  }
@@ -0,0 +1,67 @@
1
+ import { getroottermRequest, getroottermResponse } from '#shared/types/routes/termdb.getrootterm.ts'
2
+
3
+ export const api: any = {
4
+ endpoint: 'termdb/rootterm',
5
+ methods: {
6
+ get: {
7
+ init,
8
+ request: {
9
+ typeId: 'getroottermRequest'
10
+ },
11
+ response: {
12
+ typeId: 'getroottermResponse'
13
+ },
14
+ examples: [
15
+ {
16
+ request: {
17
+ body: {
18
+ genome: 'hg38-test',
19
+ dslabel: 'TermdbTest',
20
+ embedder: 'localhost',
21
+ default_rootterm: 1,
22
+ cohortValues: 'ABC'
23
+ }
24
+ },
25
+ response: {
26
+ header: { status: 200 }
27
+ }
28
+ }
29
+ ]
30
+ },
31
+ post: {
32
+ alternativeFor: 'get',
33
+ init
34
+ }
35
+ }
36
+ }
37
+
38
+ function init({ genomes }) {
39
+ return async (req: any, res: any): Promise<void> => {
40
+ const q = req.query as getroottermRequest
41
+ try {
42
+ const g = genomes[req.query.genome]
43
+ if (!g) throw 'invalid genome name'
44
+ const ds = g.datasets[req.query.dslabel]
45
+ if (!ds) throw 'invalid dataset name'
46
+ const tdb = ds.cohort.termdb
47
+ if (!tdb) throw 'invalid termdb object'
48
+
49
+ await trigger_rootterm(q, res, tdb) // as getroottermResponse
50
+ } catch (e) {
51
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
52
+ // @ts-ignore
53
+ res.send({ error: e?.message || e })
54
+ if (e instanceof Error && e.stack) console.log(e)
55
+ }
56
+ }
57
+ }
58
+
59
+ async function trigger_rootterm(
60
+ q: { cohortValues: any; treeFilter: any },
61
+ res: { send: (arg0: { lst: any }) => void },
62
+ tdb: { q: { getRootTerms: (arg0: any, arg1: any) => any } }
63
+ ) {
64
+ const cohortValues = q.cohortValues ? q.cohortValues : ''
65
+ const treeFilter = q.treeFilter ? q.treeFilter : ''
66
+ res.send({ lst: await tdb.q.getRootTerms(cohortValues, treeFilter) } as getroottermResponse)
67
+ }