@sjcrh/proteinpaint-server 2.109.1-0 → 2.110.0

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.109.1-0",
3
+ "version": "2.110.0",
4
4
  "type": "module",
5
5
  "description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
6
6
  "main": "src/app.js",
@@ -17,7 +17,7 @@
17
17
  "dev": "npm run start",
18
18
  "prestart": "tsx emitImports.js dev > server.js",
19
19
  "start": "tsx watch . /start.js",
20
- "test:unit": "tsx emitImports.js unit > serverTests.js && tsx serverTests.js && rm -rf ./cache",
20
+ "test:unit": "tsx emitImports.js unit > serverTests.js && c8 tsx serverTests.js && rm -rf ./cache",
21
21
  "getconf": "../build/getConfigProp.js",
22
22
  "doc": "../augen/build.sh routes shared/types/routes shared/checkers ../public/docs/server",
23
23
  "mjs": "esbuild \"$DIR/*.ts\" --platform=node --outdir=\"$DIR\" --format=esm",
@@ -46,6 +46,7 @@
46
46
  "@types/tough-cookie": "^4.0.5",
47
47
  "@typescript-eslint/eslint-plugin": "^8.13.0",
48
48
  "babel-loader": "^8.2.2",
49
+ "c8": "^10.1.3",
49
50
  "esbuild": "^0.19.12",
50
51
  "glob": "^10.4.5",
51
52
  "node-watch": "^0.7.1",
@@ -59,9 +60,9 @@
59
60
  },
60
61
  "dependencies": {
61
62
  "@sjcrh/augen": "2.109.1-0",
62
- "@sjcrh/proteinpaint-rust": "2.99.0",
63
- "@sjcrh/proteinpaint-shared": "2.109.1-0",
64
- "@sjcrh/proteinpaint-types": "2.109.1-0",
63
+ "@sjcrh/proteinpaint-rust": "2.110.0",
64
+ "@sjcrh/proteinpaint-shared": "2.109.1",
65
+ "@sjcrh/proteinpaint-types": "2.110.0",
65
66
  "@types/express": "^5.0.0",
66
67
  "@types/express-session": "^1.18.1",
67
68
  "better-sqlite3": "^9.4.1",
@@ -82,7 +83,7 @@
82
83
  "ky": "^1.2.1",
83
84
  "lazy": "^1.0.11",
84
85
  "micromatch": "^4.0.5",
85
- "minimatch": "^3.1.2",
86
+ "minimatch": "^10.0.1",
86
87
  "node-fetch": "^2.6.1",
87
88
  "partjson": "^0.58.2",
88
89
  "redis": "^4.7.0",
@@ -1,6 +1,6 @@
1
1
  import ky from "ky";
2
2
  import { joinUrl } from "#shared/joinUrl.js";
3
- import { run_rust_stream } from "@sjcrh/proteinpaint-rust";
3
+ import { stream_rust } from "@sjcrh/proteinpaint-rust";
4
4
  import { gdcMafPayload } from "#types/checkers";
5
5
  import { maxTotalSizeCompressed } from "./gdc.maf.ts";
6
6
  import { mayLog } from "#src/helpers.ts";
@@ -46,19 +46,26 @@ async function buildMaf(q, res, ds) {
46
46
  host: joinUrl(host.rest, "data")
47
47
  // must use the /data/ endpoint from current host
48
48
  };
49
- const rustStream = run_rust_stream("gdcmaf", JSON.stringify(arg));
50
- res.setHeader("Content-Type", "application/octet-stream");
51
- res.setHeader("Content-Disposition", "attachment; filename=cohort.maf.gz");
52
- rustStream.pipe(res);
53
- rustStream.on("end", () => {
49
+ const boundary = "GDC_MAF_MULTIPART_BOUNDARY";
50
+ res.setHeader("content-type", `multipart/mixed; boundary=${boundary}`);
51
+ res.write(`--${boundary}`);
52
+ res.write("\ncontent-disposition: attachment; filename=cohort.maf.gz");
53
+ res.write("\ncontent-type: application/octet-stream\n\n");
54
+ res.flush();
55
+ const rustStream = stream_rust("gdcmaf", JSON.stringify(arg), emitJson);
56
+ rustStream.pipe(res, { end: false });
57
+ function emitJson(data, end = true) {
58
+ res.write(`
59
+ --${boundary}`);
60
+ res.write("\ncontent-type: application/json");
61
+ const json = typeof data === "string" ? data : JSON.stringify(data || { ok: true, status: "ok" });
62
+ res.write("\n\n" + json);
63
+ res.write(`
64
+ --${boundary}--`);
54
65
  mayLog("rust gdcmaf", Date.now() - t0);
55
- res.end();
56
- });
57
- rustStream.on("error", (err) => {
58
- console.error(err);
59
- res.statusCode = 500;
60
- res.end("Internal Server Error");
61
- });
66
+ if (end)
67
+ res.end();
68
+ }
62
69
  }
63
70
  async function getFileLstUnderSizeLimit(lst, host) {
64
71
  if (lst.length == 0)
@@ -1,3 +1,4 @@
1
+ import fs from "fs";
1
2
  import path from "path";
2
3
  import { diffExpPayload } from "#types/checkers";
3
4
  import { run_rust } from "@sjcrh/proteinpaint-rust";
@@ -188,6 +189,7 @@ async function run_DE(param, ds, term_results, term_results2) {
188
189
  control: cases_string,
189
190
  data_type: "do_DE",
190
191
  input_file: q.file,
192
+ cachedir: serverconfig.cachedir,
191
193
  min_count: param.min_count,
192
194
  min_total_count: param.min_total_count,
193
195
  storage_type: param.storage_type
@@ -207,25 +209,45 @@ async function run_DE(param, ds, term_results, term_results2) {
207
209
  }
208
210
  }
209
211
  const sample_size_limit = 8;
210
- let result;
211
212
  if (group1names.length <= sample_size_limit && group2names.length <= sample_size_limit || param.method == "edgeR") {
212
213
  if (param.method == "edgeR") {
213
214
  expression_input.VarGenes = param.VarGenes;
214
215
  }
215
- const time1 = (/* @__PURE__ */ new Date()).valueOf();
216
- result = JSON.parse(
216
+ const time12 = (/* @__PURE__ */ new Date()).valueOf();
217
+ const result2 = JSON.parse(
217
218
  await run_R(path.join(serverconfig.binpath, "utils", "edge.R"), JSON.stringify(expression_input))
218
219
  );
219
- mayLog("Time taken to run edgeR:", Date.now() - time1, "ms");
220
+ mayLog("Time taken to run edgeR:", Date.now() - time12, "ms");
220
221
  param.method = "edgeR";
221
- } else {
222
- const time1 = (/* @__PURE__ */ new Date()).valueOf();
223
- result = JSON.parse(await run_rust("DEanalysis", JSON.stringify(expression_input)));
224
- mayLog("Time taken to run rust DE pipeline:", Date.now() - time1, "ms");
225
- param.method = "wilcoxon";
222
+ const ql_imagePath = path.join(serverconfig.cachedir, result2.edgeR_ql_image_name[0]);
223
+ mayLog("ql_imagePath:", ql_imagePath);
224
+ const mds_imagePath = path.join(serverconfig.cachedir, result2.edgeR_mds_image_name[0]);
225
+ mayLog("mds_imagePath:", mds_imagePath);
226
+ const mds_base64Image = await readFileAndDelete(mds_imagePath);
227
+ const ql_base64Image = await readFileAndDelete(ql_imagePath);
228
+ return {
229
+ data: result2.gene_data,
230
+ sample_size1,
231
+ sample_size2,
232
+ method: param.method,
233
+ ql_image: ql_base64Image,
234
+ // QL fit image
235
+ mds_image: mds_base64Image
236
+ // MDS image
237
+ };
226
238
  }
239
+ const time1 = (/* @__PURE__ */ new Date()).valueOf();
240
+ const result = JSON.parse(await run_rust("DEanalysis", JSON.stringify(expression_input)));
241
+ mayLog("Time taken to run rust DE pipeline:", Date.now() - time1, "ms");
242
+ param.method = "wilcoxon";
227
243
  return { data: result, sample_size1, sample_size2, method: param.method };
228
244
  }
245
+ async function readFileAndDelete(file) {
246
+ const data = await fs.promises.readFile(file);
247
+ fs.unlink(file, () => {
248
+ });
249
+ return Buffer.from(data).toString("base64");
250
+ }
229
251
  export {
230
252
  api
231
253
  };
@@ -3,6 +3,7 @@ import { getData } from "../src/termdb.matrix.js";
3
3
  import { boxplot_getvalue } from "../src/utils.js";
4
4
  import { sortKey2values } from "./termdb.violin.ts";
5
5
  import { roundValueAuto } from "#shared/roundValue.js";
6
+ const minSampleSize = 5;
6
7
  const api = {
7
8
  endpoint: "termdb/boxplot",
8
9
  methods: {
@@ -124,7 +125,7 @@ function setHiddenPlots(term, plots) {
124
125
  return plots;
125
126
  }
126
127
  function setDescrStats(boxplot, sortedValues) {
127
- if (sortedValues.length < 5)
128
+ if (sortedValues.length < minSampleSize)
128
129
  return [{ id: "total", label: "Total", value: sortedValues.length }];
129
130
  const mean = sortedValues.reduce((s2, i) => s2 + i, 0) / sortedValues.length;
130
131
  let s = 0;
@@ -10,6 +10,7 @@ import { getResult as getResultGene } from "#src/gene.js";
10
10
  import { TermTypes, NUMERIC_DICTIONARY_TERM } from "#shared/terms.js";
11
11
  import { getData } from "#src/termdb.matrix.js";
12
12
  import { termType2label } from "#shared/terms.js";
13
+ import { mayLog } from "#src/helpers.ts";
13
14
  const api = {
14
15
  endpoint: "termdb/cluster",
15
16
  methods: {
@@ -74,8 +75,10 @@ async function getResult(q, ds, genome) {
74
75
  ;
75
76
  ({ term2sample2value, byTermId, bySampleId } = await ds.queries[q.dataType].get(_q));
76
77
  }
78
+ const removedHierClusterTerms = [];
77
79
  for (const [term, obj] of term2sample2value) {
78
80
  if (Object.keys(obj).length === 0) {
81
+ removedHierClusterTerms.push(term);
79
82
  term2sample2value.delete(term);
80
83
  delete byTermId[term];
81
84
  }
@@ -88,9 +91,11 @@ async function getResult(q, ds, genome) {
88
91
  }
89
92
  const t = Date.now();
90
93
  const clustering = await doClustering(term2sample2value, q, Object.keys(bySampleId).length);
91
- if (serverconfig.debugmode)
92
- console.log("clustering done:", Date.now() - t, "ms");
93
- return { clustering, byTermId, bySampleId };
94
+ mayLog("clustering done:", Date.now() - t, "ms");
95
+ const result = { clustering, byTermId, bySampleId };
96
+ if (removedHierClusterTerms.length)
97
+ result.removedHierClusterTerms = removedHierClusterTerms;
98
+ return result;
94
99
  }
95
100
  async function getNumericDictTermAnnotation(q, ds, genome) {
96
101
  const getDataArgs = {
@@ -46,14 +46,7 @@ async function validate_query_singleSampleMutation(ds, genome) {
46
46
  gdcValidate_query_singleSampleMutation(ds, genome);
47
47
  } else if (_q.src == "native") {
48
48
  _q.get = async (q) => {
49
- let fileName = q.sample;
50
- if (ds.cohort?.termdb?.q?.sampleName2id) {
51
- fileName = ds.cohort.termdb.q.sampleName2id(q.sample);
52
- if (fileName == void 0) {
53
- return [];
54
- }
55
- }
56
- const file = path.join(serverconfig.tpmasterdir, _q.folder, fileName.toString());
49
+ const file = path.join(serverconfig.tpmasterdir, _q.folder, q.sample);
57
50
  try {
58
51
  await fs.promises.stat(file);
59
52
  } catch (e) {
@@ -8,6 +8,7 @@ import summaryStats from "#shared/descriptive.stats.js";
8
8
  import { isNumericTerm } from "#shared/terms.js";
9
9
  import { getBinsDensity } from "#shared/violin.bins.js";
10
10
  import { numericBins, parseValues } from "./termdb.boxplot.ts";
11
+ const minSampleSize = 5;
11
12
  const api = {
12
13
  endpoint: "termdb/violin",
13
14
  methods: {
@@ -193,7 +194,7 @@ function createCanvasImg(q, result, ds) {
193
194
  ctx.strokeStyle = "rgba(0,0,0,0.8)";
194
195
  ctx.lineWidth = q.strokeWidth / q.devicePixelRatio;
195
196
  ctx.globalAlpha = 0.5;
196
- ctx.fillStyle = "#ffe6e6";
197
+ ctx.fillStyle = plot.values.length <= minSampleSize ? "black" : "#ffe6e6";
197
198
  if (q.devicePixelRatio != 1) {
198
199
  ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
199
200
  }
@@ -220,7 +221,7 @@ function createCanvasImg(q, result, ds) {
220
221
  ctx.stroke();
221
222
  });
222
223
  plot.src = canvas.toDataURL();
223
- plot.density = getBinsDensity(axisScale, plot, q.isKDE, q.ticks);
224
+ plot.density = getBinsDensity(plot, q.isKDE, q.ticks);
224
225
  plot.summaryStats = summaryStats(plot.values).values;
225
226
  delete plot.values;
226
227
  }