@sjcrh/proteinpaint-shared 2.187.0 → 2.188.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/README.md +10 -2
- package/constants/AiHisto.ts +27 -0
- package/constants/README.md +11 -0
- package/devTs.ts +3 -0
- package/dist/constants/AiHisto.d.ts +23 -0
- package/dist/constants/AiHisto.js +31 -0
- package/dist/constants/AiHisto.js.map +7 -0
- package/dist/src/aiHisto.d.ts +5 -0
- package/dist/src/aiHisto.js +15 -0
- package/dist/src/aiHisto.js.map +7 -0
- package/dist/src/bulk.cnv.js +83 -0
- package/dist/src/bulk.cnv.js.map +7 -0
- package/dist/src/bulk.del.js +119 -0
- package/dist/src/bulk.del.js.map +7 -0
- package/dist/src/bulk.itd.js +119 -0
- package/dist/src/bulk.itd.js.map +7 -0
- package/dist/src/bulk.js +183 -0
- package/dist/src/bulk.js.map +7 -0
- package/dist/src/bulk.snv.js +175 -0
- package/dist/src/bulk.snv.js.map +7 -0
- package/dist/src/bulk.sv.js +266 -0
- package/dist/src/bulk.sv.js.map +7 -0
- package/dist/src/bulk.svjson.js +151 -0
- package/dist/src/bulk.svjson.js.map +7 -0
- package/dist/src/bulk.trunc.js +122 -0
- package/dist/src/bulk.trunc.js.map +7 -0
- package/dist/src/clustering.js +71 -0
- package/dist/src/clustering.js.map +7 -0
- package/dist/src/common.js +1302 -0
- package/dist/src/common.js.map +7 -0
- package/dist/src/compute.percentile.js +10 -0
- package/dist/src/compute.percentile.js.map +7 -0
- package/dist/src/doc.d.ts +7 -0
- package/dist/src/doc.js +10 -0
- package/dist/src/doc.js.map +7 -0
- package/dist/src/fetch-helpers.js +177 -0
- package/dist/src/fetch-helpers.js.map +7 -0
- package/dist/src/fileSize.js +10 -0
- package/dist/src/fileSize.js.map +7 -0
- package/dist/src/filter.d.ts +62 -0
- package/dist/src/filter.js +194 -0
- package/dist/src/filter.js.map +7 -0
- package/dist/src/hash.js +20 -0
- package/dist/src/hash.js.map +7 -0
- package/dist/src/helpers.js +66 -0
- package/dist/src/helpers.js.map +7 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.js +27 -0
- package/dist/src/index.js.map +7 -0
- package/dist/src/joinUrl.d.ts +1 -0
- package/dist/src/joinUrl.js +17 -0
- package/dist/src/joinUrl.js.map +7 -0
- package/dist/src/mds3tk.js +64 -0
- package/dist/src/mds3tk.js.map +7 -0
- package/dist/src/roundValue.js +57 -0
- package/dist/src/roundValue.js.map +7 -0
- package/dist/src/termdb.bins.js +272 -0
- package/dist/src/termdb.bins.js.map +7 -0
- package/dist/src/termdb.initbinconfig.js +79 -0
- package/dist/src/termdb.initbinconfig.js.map +7 -0
- package/dist/src/termdb.usecase.js +239 -0
- package/dist/src/termdb.usecase.js.map +7 -0
- package/dist/src/terms.d.ts +83 -0
- package/dist/src/terms.js +327 -0
- package/dist/src/terms.js.map +7 -0
- package/dist/src/time.d.ts +9 -0
- package/dist/src/time.js +23 -0
- package/dist/src/time.js.map +7 -0
- package/dist/src/tree.js +82 -0
- package/dist/src/tree.js.map +7 -0
- package/dist/src/urljson.d.ts +8 -0
- package/dist/src/urljson.js +31 -0
- package/dist/src/urljson.js.map +7 -0
- package/dist/src/vcf.ann.js +56 -0
- package/dist/src/vcf.ann.js.map +7 -0
- package/dist/src/vcf.csq.js +82 -0
- package/dist/src/vcf.csq.js.map +7 -0
- package/dist/src/vcf.info.js +40 -0
- package/dist/src/vcf.info.js.map +7 -0
- package/dist/src/vcf.js +439 -0
- package/dist/src/vcf.js.map +7 -0
- package/dist/src/vcf.type.js +17 -0
- package/dist/src/vcf.type.js.map +7 -0
- package/package.json +20 -11
- package/src/bulk.cnv.js +0 -86
- package/src/bulk.del.js +0 -124
- package/src/bulk.itd.js +0 -123
- package/src/bulk.js +0 -197
- package/src/bulk.snv.js +0 -271
- package/src/bulk.sv.js +0 -276
- package/src/bulk.svjson.js +0 -164
- package/src/bulk.trunc.js +0 -132
- package/src/clustering.js +0 -66
- package/src/common.js +0 -1608
- package/src/compute.percentile.js +0 -11
- package/src/doc.js +0 -6
- package/src/fetch-helpers.js +0 -323
- package/src/fileSize.js +0 -6
- package/src/filter.js +0 -221
- package/src/hash.js +0 -21
- package/src/helpers.js +0 -88
- package/src/index.js +0 -26
- package/src/joinUrl.js +0 -14
- package/src/mds3tk.js +0 -100
- package/src/roundValue.js +0 -94
- package/src/termdb.bins.js +0 -456
- package/src/termdb.initbinconfig.js +0 -130
- package/src/termdb.usecase.js +0 -344
- package/src/terms.js +0 -341
- package/src/time.js +0 -22
- package/src/tree.js +0 -138
- package/src/urljson.js +0 -41
- package/src/vcf.ann.js +0 -62
- package/src/vcf.csq.js +0 -153
- package/src/vcf.info.js +0 -50
- package/src/vcf.js +0 -654
- package/src/vcf.type.js +0 -24
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// compute the percentile value from an array of values
|
|
2
|
-
// sorted parameter allows to skip sorting if array is pre-sorted
|
|
3
|
-
// source: https://www.dummies.com/article/academics-the-arts/math/statistics/how-to-calculate-percentiles-in-statistics-169783/
|
|
4
|
-
export default function computePercentile(values, percentile, sorted = false) {
|
|
5
|
-
if (!sorted) values.sort((a, b) => a - b)
|
|
6
|
-
const index = Math.abs((percentile / 100) * values.length - 1)
|
|
7
|
-
const value = Number.isInteger(index)
|
|
8
|
-
? (values[index] + values[index + 1]) / 2
|
|
9
|
-
: values[Math.ceil(index)]
|
|
10
|
-
return value
|
|
11
|
-
}
|
package/src/doc.js
DELETED
package/src/fetch-helpers.js
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import { hash } from "./hash.js"
|
|
2
|
-
import { encode } from "./urljson.js"
|
|
3
|
-
import { deepFreeze } from "./helpers.js"
|
|
4
|
-
|
|
5
|
-
/*
|
|
6
|
-
ezFetch()
|
|
7
|
-
fetch wrapper with automatic response content-type detection and handling
|
|
8
|
-
|
|
9
|
-
- this addresses issues with ky.json() or got.json(), where a HTTP 404 NOT FOUND
|
|
10
|
-
response with text/html can break error handling/logging, making it harder to debug
|
|
11
|
-
|
|
12
|
-
- this also automatically handles multipart responses
|
|
13
|
-
|
|
14
|
-
- NOTE: for backend, use xfetch() instead, it uses ky() and its built-in retry support
|
|
15
|
-
|
|
16
|
-
arguments:
|
|
17
|
-
url
|
|
18
|
-
init{headers?, body?}
|
|
19
|
-
- first two arguments are same as native fetch
|
|
20
|
-
*/
|
|
21
|
-
export async function ezFetch(_url, init = {}, opts = {}) {
|
|
22
|
-
const url = opts.autoMethod ? mayAdjustRequest(_url, init) : _url
|
|
23
|
-
if (typeof init.body === "object") init.body = JSON.stringify(init.body)
|
|
24
|
-
|
|
25
|
-
return fetch(url, init).then(async (r) => {
|
|
26
|
-
const response = await processResponse(r)
|
|
27
|
-
if (!r.ok) {
|
|
28
|
-
console.log("ezFetch error " + r.status)
|
|
29
|
-
console.log(response)
|
|
30
|
-
throw response
|
|
31
|
-
}
|
|
32
|
-
return response
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function mayAdjustRequest(url, init) {
|
|
37
|
-
const method = init.method?.toUpperCase() || "GET"
|
|
38
|
-
if (method == "POST") {
|
|
39
|
-
if (!init.headers) init.headers = {}
|
|
40
|
-
if (init.body) {
|
|
41
|
-
if (!init.headers["content-type"])
|
|
42
|
-
init.headers["content-type"] = "application/json"
|
|
43
|
-
if (init.headers["content-type"].toLowerCase() == "application/json") {
|
|
44
|
-
// if consumer code has pre-encoded the body, parse to verify correctness
|
|
45
|
-
if (typeof init.body == "string") init.body = JSON.parse(init.body)
|
|
46
|
-
init.body = JSON.stringify(init.body)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return url
|
|
50
|
-
}
|
|
51
|
-
// default to GET method per native fetch
|
|
52
|
-
if (init.body) {
|
|
53
|
-
if (typeof init.body != "object") throw `init.body should be an object`
|
|
54
|
-
// init.body should be an object, to be converted to GET URL search parameter strings
|
|
55
|
-
if (!url.includes("?")) url += "?"
|
|
56
|
-
return `${url}${encode(init.body)}`
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/*
|
|
61
|
-
r: a native fetch response argument
|
|
62
|
-
|
|
63
|
-
potentially allow "application/octet-stream" as response type
|
|
64
|
-
in such case it will not try to parse it as json
|
|
65
|
-
also the caller should just call dofetch2() without a serverData{}
|
|
66
|
-
rather than dofetch3
|
|
67
|
-
*/
|
|
68
|
-
export async function processResponse(r) {
|
|
69
|
-
// if (!r.ok) {
|
|
70
|
-
// throw new Error(`HTTP error! status: ${r.status}`)
|
|
71
|
-
// }
|
|
72
|
-
const ct = r.headers.get("content-type") // content type is always present
|
|
73
|
-
if (!ct) throw `missing response.header['content-type']`
|
|
74
|
-
if (ct.includes("/json")) {
|
|
75
|
-
const payload = await r.json()
|
|
76
|
-
// server should use a standard HTTP response status 400+, 500+
|
|
77
|
-
// so that !r.ok will already be caught when wrapping fetch with try-catch
|
|
78
|
-
// if (payload.error || payload.status == '') throw payload
|
|
79
|
-
// if (payload.status === 'error') throw payload.message || payload
|
|
80
|
-
return payload
|
|
81
|
-
}
|
|
82
|
-
if (ct.includes("/text") || ct.includes("text/")) {
|
|
83
|
-
return r.text()
|
|
84
|
-
}
|
|
85
|
-
if (ct.includes("multipart")) {
|
|
86
|
-
if (ct.startsWith("multipart/form-data")) return processFormData(r)
|
|
87
|
-
else throw `cannot handle response content-type: '${ct}'`
|
|
88
|
-
}
|
|
89
|
-
if (ct == "application/x-ndjson-nestedkey") {
|
|
90
|
-
return processNDJSON_nestedKey(r)
|
|
91
|
-
}
|
|
92
|
-
// call blob() as catch-all
|
|
93
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Response
|
|
94
|
-
return r.blob()
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/*
|
|
98
|
-
expected response format
|
|
99
|
-
--boundary-text-from-HTTP-headers-content-type
|
|
100
|
-
header-key1: header-value1
|
|
101
|
-
header-key2: header-value2
|
|
102
|
-
|
|
103
|
-
...json, text, blob, value, etc...
|
|
104
|
-
--boundary-text-from-HTTP-headers-content-type
|
|
105
|
-
... same format as previous chunk ...
|
|
106
|
-
|
|
107
|
-
--boundary-text-from-HTTP-headers-content-type--
|
|
108
|
-
*/
|
|
109
|
-
|
|
110
|
-
export async function processFormData(res) {
|
|
111
|
-
const decoder = new TextDecoder()
|
|
112
|
-
const data = {}
|
|
113
|
-
try {
|
|
114
|
-
const form = await res.formData()
|
|
115
|
-
// The key of each form entry is a string, and the value is either a string or a Blob.
|
|
116
|
-
// see https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries
|
|
117
|
-
for (const [key, value] of form.entries()) {
|
|
118
|
-
if (value.type) {
|
|
119
|
-
// value is a Blob
|
|
120
|
-
data[key] = { headers: { "content-type": value.type }, body: value }
|
|
121
|
-
} else {
|
|
122
|
-
// value is a string, assume to be application/x-jsonlines (one json encoded value per line)
|
|
123
|
-
// and convert into an array of json-decoded values
|
|
124
|
-
const body = !value ? [] : value.trim().split("\n").map(JSON.parse)
|
|
125
|
-
data[key] = { headers: { "content-type": "application/json" }, body }
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return data
|
|
129
|
-
} catch (e) {
|
|
130
|
-
throw e
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function processNDJSON_nestedKey(r) {
|
|
135
|
-
// 1. Pipe through TextDecoder to convert bytes to text
|
|
136
|
-
const stream = r.body.pipeThrough(new TextDecoderStream())
|
|
137
|
-
const reader = stream.getReader()
|
|
138
|
-
let rootObj = {}
|
|
139
|
-
|
|
140
|
-
let buffer = ""
|
|
141
|
-
|
|
142
|
-
while (true) {
|
|
143
|
-
const { value, done } = await reader.read()
|
|
144
|
-
if (done) break
|
|
145
|
-
|
|
146
|
-
// 2. Add new chunk to buffer
|
|
147
|
-
buffer += value
|
|
148
|
-
|
|
149
|
-
// 3. Split by newline
|
|
150
|
-
let parts = buffer.split("\n")
|
|
151
|
-
|
|
152
|
-
// 4. Keep the last partial line in the buffer
|
|
153
|
-
buffer = parts.pop()
|
|
154
|
-
|
|
155
|
-
// 5. Process complete lines
|
|
156
|
-
for (const line of parts) {
|
|
157
|
-
if (line.trim()) {
|
|
158
|
-
const [keys, data] = JSON.parse(line) //; console.log(143, keys, data) // Process JSON data
|
|
159
|
-
if (!keys.length) rootObj = data
|
|
160
|
-
else {
|
|
161
|
-
const lastKey = keys.pop()
|
|
162
|
-
let target = rootObj
|
|
163
|
-
for (const k of keys) target = target[k]
|
|
164
|
-
target[lastKey] = data
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return rootObj
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// key: request object reference or computed string dataName
|
|
173
|
-
// value: {
|
|
174
|
-
// response: fetch promise or response,
|
|
175
|
-
// exp: expiration timestamp
|
|
176
|
-
// }
|
|
177
|
-
const dataCache = new Map()
|
|
178
|
-
// maximum number of cached dataNames, oldest will be deleted if this is exceeded
|
|
179
|
-
const maxNumOfDataKeys = 10
|
|
180
|
-
const cacheLifetime = 1000 * 60 * 5
|
|
181
|
-
/*
|
|
182
|
-
memFetch()
|
|
183
|
-
- fetch wrapper that saves cached responses into memory and recovers them for matching subsequent requests
|
|
184
|
-
- recommended for caching responses in the backend, with the opts.q argument to cache per expressjs request object
|
|
185
|
-
- should call deleteCache(request) at the end of request handling, to free unneeded cache
|
|
186
|
-
|
|
187
|
-
See the usage note for getDataName() to avoid non-unique request/response.
|
|
188
|
-
|
|
189
|
-
Arguments:
|
|
190
|
-
url
|
|
191
|
-
init{headers?, body?}
|
|
192
|
-
- first two arguments are same as native fetch
|
|
193
|
-
- when passing opts.client, may include other applicable options inside the init{} object, such as retry
|
|
194
|
-
|
|
195
|
-
opts{client}
|
|
196
|
-
|
|
197
|
-
client: use this http client instead of native fetch
|
|
198
|
-
- since fetch-helpers is shared between server and frontend workspaces,
|
|
199
|
-
cannot directly import non-native modules at the beginning of this code file
|
|
200
|
-
- for server side usage, client may be `xfetch()`, `ky` or other libraries
|
|
201
|
-
*/
|
|
202
|
-
export async function memFetch(url, init, opts = {}) {
|
|
203
|
-
if (typeof init.body === "object") init.body = JSON.stringify(init.body)
|
|
204
|
-
const dataKey = opts.q || (await getDataName(url, init))
|
|
205
|
-
const { response, exp } = dataCache.get(dataKey) || {}
|
|
206
|
-
const now = Date.now()
|
|
207
|
-
let result = response // either a Promise or actual data
|
|
208
|
-
|
|
209
|
-
if (result) {
|
|
210
|
-
// extend the expiration, since exp is more about managing the cache size
|
|
211
|
-
// and not the validity of the cached response. A response for the current
|
|
212
|
-
// dataName req.url + body + headers is technically valid until a new data version
|
|
213
|
-
// gets published.
|
|
214
|
-
dataCache.set(dataKey, { response, exp: now + cacheLifetime })
|
|
215
|
-
return result
|
|
216
|
-
} else {
|
|
217
|
-
try {
|
|
218
|
-
// IMPORTANT: do not await so that this same promise may be reused
|
|
219
|
-
// by subsequent requests with the same dataKey
|
|
220
|
-
result = opts.client
|
|
221
|
-
? opts
|
|
222
|
-
.client(url, init, Object.assign(opts, { client: undefined }))
|
|
223
|
-
.then((response) => {
|
|
224
|
-
// replace the cached promise result with the actual data,
|
|
225
|
-
// since persisting a cached promise for a long time is likely not best practice
|
|
226
|
-
dataCache.set(dataKey, {
|
|
227
|
-
response,
|
|
228
|
-
exp: Date.now() + cacheLifetime,
|
|
229
|
-
})
|
|
230
|
-
return response
|
|
231
|
-
})
|
|
232
|
-
: fetch(url, init)
|
|
233
|
-
.then(async (r) => {
|
|
234
|
-
const response = await processResponse(r)
|
|
235
|
-
if (!r.ok) {
|
|
236
|
-
console.trace(response)
|
|
237
|
-
throw (
|
|
238
|
-
"memFetch error " +
|
|
239
|
-
r.status +
|
|
240
|
-
": " +
|
|
241
|
-
(typeof response == "object"
|
|
242
|
-
? response.message || response.error
|
|
243
|
-
: response)
|
|
244
|
-
)
|
|
245
|
-
}
|
|
246
|
-
// replace the cached promise result with the actual data,
|
|
247
|
-
// since persisting a cached promise for a long time is likely not best practice
|
|
248
|
-
dataCache.set(dataKey, {
|
|
249
|
-
response: deepFreeze(response),
|
|
250
|
-
exp: Date.now() + cacheLifetime,
|
|
251
|
-
})
|
|
252
|
-
return response
|
|
253
|
-
})
|
|
254
|
-
.catch((e) => {
|
|
255
|
-
if (dataCache.get(dataKey)) dataCache.delete(dataKey)
|
|
256
|
-
throw e
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
dataCache.set(dataKey, {
|
|
260
|
-
response: result,
|
|
261
|
-
exp: Date.now() + cacheLifetime,
|
|
262
|
-
})
|
|
263
|
-
manageCacheSize(now)
|
|
264
|
-
return result
|
|
265
|
-
} catch (e) {
|
|
266
|
-
// delete this cache only if it is a promise;
|
|
267
|
-
// do not delete a valid resolved data cache
|
|
268
|
-
if (dataCache.get(dataKey) instanceof Promise) dataCache.delete(dataKey)
|
|
269
|
-
throw e
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export function deleteCache(key) {
|
|
275
|
-
dataCache.delete(key)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function manageCacheSize(_now) {
|
|
279
|
-
const now = _now || Date.now()
|
|
280
|
-
const keyExp = []
|
|
281
|
-
for (const [key, result] of dataCache.entries()) {
|
|
282
|
-
if (result.exp < now) dataCache.delete(key)
|
|
283
|
-
else keyExp.push({ key, exp: result.exp })
|
|
284
|
-
}
|
|
285
|
-
if (dataCache.size > maxNumOfDataKeys) {
|
|
286
|
-
const oldestEntries = keyExp
|
|
287
|
-
.sort((a, b) => a.exp - b.exp)
|
|
288
|
-
.slice(maxNumOfDataKeys)
|
|
289
|
-
for (const entry of oldestEntries) dataCache.delete(entry.key)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/*
|
|
294
|
-
NOTE: When used in client-side code, an HttpOnly cookie for a logged in user will not be
|
|
295
|
-
tracked in init.headers below.
|
|
296
|
-
*/
|
|
297
|
-
export async function getDataName(url, init) {
|
|
298
|
-
// IMPORTANT: must ensure dataName is unique to either public or logged-in user
|
|
299
|
-
const dataName =
|
|
300
|
-
url +
|
|
301
|
-
" | " +
|
|
302
|
-
init.method +
|
|
303
|
-
" | " +
|
|
304
|
-
init.body +
|
|
305
|
-
" | " +
|
|
306
|
-
JSON.stringify(init.headers)
|
|
307
|
-
return await hash(dataName)
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
export function clearMemFetchDataCache(opts = {}) {
|
|
312
|
-
if (!opts.serverData) {
|
|
313
|
-
dataCache.clear()
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
if (typeof opts.serverData != "object")
|
|
317
|
-
throw `opts.serverData is not an object`
|
|
318
|
-
for (const k of Object.keys(opts.serverData)) {
|
|
319
|
-
delete opts.serverData[k]
|
|
320
|
-
}
|
|
321
|
-
if (optsServerDataNames.has(opts.serverData))
|
|
322
|
-
optsServerDataNames.delete(opts.serverData)
|
|
323
|
-
}
|
package/src/fileSize.js
DELETED
package/src/filter.js
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
function getFilteredSamples(sampleAnno, filter) {
|
|
2
|
-
setDatasetAnnotations(filter)
|
|
3
|
-
const samples = /* @__PURE__ */ new Set()
|
|
4
|
-
for (const anno of sampleAnno) {
|
|
5
|
-
if (samples.has(anno.sample)) continue
|
|
6
|
-
const data = anno.s || anno.data
|
|
7
|
-
if (data && sample_match_termvaluesetting(data, filter)) {
|
|
8
|
-
samples.add(anno.sample)
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
return samples
|
|
12
|
-
}
|
|
13
|
-
function sample_match_termvaluesetting(
|
|
14
|
-
row,
|
|
15
|
-
filter,
|
|
16
|
-
_term = null,
|
|
17
|
-
sample = null
|
|
18
|
-
) {
|
|
19
|
-
const lst = filter.type == "tvslst" ? filter.lst : [filter]
|
|
20
|
-
let numberofmatchedterms = 0
|
|
21
|
-
for (const item of lst) {
|
|
22
|
-
if ("type" in item && item.type == "tvslst") {
|
|
23
|
-
if (sample_match_termvaluesetting(row, item, _term, sample)) {
|
|
24
|
-
numberofmatchedterms++
|
|
25
|
-
}
|
|
26
|
-
} else {
|
|
27
|
-
const itemCopy = JSON.parse(JSON.stringify(item))
|
|
28
|
-
const t = itemCopy.tvs
|
|
29
|
-
if (_term && t.term) {
|
|
30
|
-
if (!(_term.name == t.term.name && _term.type == t.term.type)) {
|
|
31
|
-
numberofmatchedterms++
|
|
32
|
-
continue
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
let samplevalue
|
|
36
|
-
if (_term && !t.term) {
|
|
37
|
-
if (t.term$type && t.term$type !== _term.type) {
|
|
38
|
-
numberofmatchedterms++
|
|
39
|
-
continue
|
|
40
|
-
}
|
|
41
|
-
t.term = _term
|
|
42
|
-
samplevalue =
|
|
43
|
-
typeof row === "object" && t.term.id in row ? row[t.term.id] : row
|
|
44
|
-
} else if (sample && t.term.$id) {
|
|
45
|
-
samplevalue = sample[t.term.$id].value
|
|
46
|
-
} else {
|
|
47
|
-
samplevalue = t.term.id in row ? row[t.term.id] : row
|
|
48
|
-
}
|
|
49
|
-
setDatasetAnnotations(itemCopy)
|
|
50
|
-
let thistermmatch
|
|
51
|
-
if (t.term.type == "categorical") {
|
|
52
|
-
if (samplevalue === void 0) continue
|
|
53
|
-
thistermmatch = t.valueset.has(samplevalue)
|
|
54
|
-
} else if (t.term.type == "integer" || t.term.type == "float") {
|
|
55
|
-
if (samplevalue === void 0) continue
|
|
56
|
-
for (const range of t.ranges) {
|
|
57
|
-
if ("value" in range) {
|
|
58
|
-
thistermmatch = samplevalue === range.value
|
|
59
|
-
if (thistermmatch) break
|
|
60
|
-
} else if (samplevalue == range.name) {
|
|
61
|
-
thistermmatch = true
|
|
62
|
-
break
|
|
63
|
-
} else {
|
|
64
|
-
if (t.term.values) {
|
|
65
|
-
const v = t.term.values[samplevalue.toString()]
|
|
66
|
-
if (v && v.uncomputable) {
|
|
67
|
-
continue
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
let left, right
|
|
71
|
-
if (range.startunbounded) {
|
|
72
|
-
left = true
|
|
73
|
-
} else if ("start" in range) {
|
|
74
|
-
if (range.startinclusive) {
|
|
75
|
-
left = samplevalue >= range.start
|
|
76
|
-
} else {
|
|
77
|
-
left = samplevalue > range.start
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (range.stopunbounded) {
|
|
81
|
-
right = true
|
|
82
|
-
} else if ("stop" in range) {
|
|
83
|
-
if (range.stopinclusive) {
|
|
84
|
-
right = samplevalue <= range.stop
|
|
85
|
-
} else {
|
|
86
|
-
right = samplevalue < range.stop
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
thistermmatch = left && right
|
|
90
|
-
}
|
|
91
|
-
if (thistermmatch) break
|
|
92
|
-
}
|
|
93
|
-
} else if (t.term.type == "condition") {
|
|
94
|
-
const key = getPrecomputedKey(t)
|
|
95
|
-
const anno = samplevalue && samplevalue[key]
|
|
96
|
-
if (anno) {
|
|
97
|
-
thistermmatch = Array.isArray(anno)
|
|
98
|
-
? t.values.find((d) => anno.includes(d.key))
|
|
99
|
-
: t.values.find((d) => d.key == anno)
|
|
100
|
-
}
|
|
101
|
-
} else if (t.term.type == "geneVariant") {
|
|
102
|
-
const svalues = samplevalue.values || [samplevalue]
|
|
103
|
-
for (const sv of svalues) {
|
|
104
|
-
thistermmatch =
|
|
105
|
-
t.values.find(
|
|
106
|
-
(v) =>
|
|
107
|
-
v.dt == sv.dt &&
|
|
108
|
-
(!v.origin || sv.origin == v.origin) &&
|
|
109
|
-
(!v.mclasslst || v.mclasslst.includes(sv.class))
|
|
110
|
-
) && true
|
|
111
|
-
if (thistermmatch) break
|
|
112
|
-
}
|
|
113
|
-
} else {
|
|
114
|
-
throw "unknown term type [sample_match_termvaluesetting() shared/utils/src/filter.ts]"
|
|
115
|
-
}
|
|
116
|
-
if (t.isnot) {
|
|
117
|
-
thistermmatch = !thistermmatch
|
|
118
|
-
}
|
|
119
|
-
if (thistermmatch) numberofmatchedterms++
|
|
120
|
-
}
|
|
121
|
-
if (filter.join == "or") {
|
|
122
|
-
if (numberofmatchedterms && filter.in) return true
|
|
123
|
-
if (!numberofmatchedterms && !filter.in) return true
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (!("in" in filter)) filter.in = true
|
|
127
|
-
return filter.in == (numberofmatchedterms == lst.length)
|
|
128
|
-
}
|
|
129
|
-
function setDatasetAnnotations(item, ds = null) {
|
|
130
|
-
if (item.type == "tvslst") {
|
|
131
|
-
for (const subitem of item.lst) {
|
|
132
|
-
setDatasetAnnotations(subitem, ds)
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
if (ds && typeof ds.setAnnoByTermId == "function") {
|
|
136
|
-
ds.setAnnoByTermId(item.tvs.term.id)
|
|
137
|
-
}
|
|
138
|
-
if (item.tvs.term.type == "categorical") {
|
|
139
|
-
const tvsAny = item.tvs
|
|
140
|
-
tvsAny.valueset = new Set(tvsAny.values.map((i) => i.key))
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function getPrecomputedKey(q) {
|
|
145
|
-
const precomputedKey =
|
|
146
|
-
q.bar_by_children && q.value_by_max_grade
|
|
147
|
-
? "childrenAtMaxGrade"
|
|
148
|
-
: q.bar_by_children && q.value_by_most_recent
|
|
149
|
-
? "childrenAtMostRecent"
|
|
150
|
-
: q.bar_by_children && q.value_by_computable_grade
|
|
151
|
-
? "children"
|
|
152
|
-
: q.bar_by_grade && q.value_by_max_grade
|
|
153
|
-
? "maxGrade"
|
|
154
|
-
: q.bar_by_grade && q.value_by_most_recent
|
|
155
|
-
? "mostRecentGrades"
|
|
156
|
-
: q.bar_by_grade && q.value_by_computable_grade
|
|
157
|
-
? "computableGrades"
|
|
158
|
-
: ""
|
|
159
|
-
if (!precomputedKey) throw `unknown condition term bar_by_* and/or value_by_*`
|
|
160
|
-
return precomputedKey
|
|
161
|
-
}
|
|
162
|
-
function filterJoin(lst) {
|
|
163
|
-
if (!lst || lst.length == 0) return
|
|
164
|
-
let f = JSON.parse(JSON.stringify(lst[0]))
|
|
165
|
-
if (lst.length == 1) return f
|
|
166
|
-
if (f.lst.length < 2) {
|
|
167
|
-
if (f.join !== "")
|
|
168
|
-
throw 'filter.join must be an empty string "" when filter.lst.length < 2'
|
|
169
|
-
f.join = "and"
|
|
170
|
-
} else if (f.join == "or") {
|
|
171
|
-
f = {
|
|
172
|
-
type: "tvslst",
|
|
173
|
-
join: "and",
|
|
174
|
-
in: true,
|
|
175
|
-
lst: [f],
|
|
176
|
-
}
|
|
177
|
-
} else if (f.join != "and") {
|
|
178
|
-
throw 'filter.join must be either "and" or "or" when .lst length > 1'
|
|
179
|
-
}
|
|
180
|
-
for (let i = 1; i < lst.length; i++) {
|
|
181
|
-
const f2 = JSON.parse(JSON.stringify(lst[i]))
|
|
182
|
-
if (f2.join == "or") f.lst.push(f2)
|
|
183
|
-
else f.lst.push(...f2.lst)
|
|
184
|
-
}
|
|
185
|
-
if (f.lst.length == 1 && f.lst[0].type == "tvs") {
|
|
186
|
-
f.join = ""
|
|
187
|
-
}
|
|
188
|
-
return f
|
|
189
|
-
}
|
|
190
|
-
function getWrappedTvslst(lst = [], join = "", $id = null) {
|
|
191
|
-
const filter = {
|
|
192
|
-
type: "tvslst",
|
|
193
|
-
in: true,
|
|
194
|
-
join,
|
|
195
|
-
lst,
|
|
196
|
-
}
|
|
197
|
-
if ($id !== null) filter.$id = $id
|
|
198
|
-
return filter
|
|
199
|
-
}
|
|
200
|
-
function validateTermCollectionTvs(lst1, lst2) {
|
|
201
|
-
if (!Array.isArray(lst1)) throw new Error("numerator not array")
|
|
202
|
-
if (!Array.isArray(lst2)) throw new Error("denominator not array")
|
|
203
|
-
if (lst1.length == 0) throw new Error("numerator empty")
|
|
204
|
-
if (lst2.length == 0) throw new Error("denominator empty")
|
|
205
|
-
if (lst1.length > lst2.length)
|
|
206
|
-
throw new Error("numerator longer than denominator")
|
|
207
|
-
for (const s of lst1) {
|
|
208
|
-
if (typeof s != "string") throw new Error("one of numerator not string")
|
|
209
|
-
if (!s) throw new Error("empty string in numerator")
|
|
210
|
-
if (!lst2.includes(s))
|
|
211
|
-
throw new Error("one of numerator not in denominator")
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
export {
|
|
215
|
-
filterJoin,
|
|
216
|
-
getFilteredSamples,
|
|
217
|
-
getWrappedTvslst,
|
|
218
|
-
sample_match_termvaluesetting,
|
|
219
|
-
setDatasetAnnotations,
|
|
220
|
-
validateTermCollectionTvs,
|
|
221
|
-
}
|
package/src/hash.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const encoder = new TextEncoder()
|
|
2
|
-
|
|
3
|
-
export async function hash(message) {
|
|
4
|
-
const msgUint8 = encoder.encode(message) // encode as (utf-8) Uint8Array
|
|
5
|
-
const hashBuffer = await crypto.subtle.digest("SHA-1", msgUint8) // hash the message
|
|
6
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array
|
|
7
|
-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("") // convert bytes to hex string
|
|
8
|
-
return hexToBase64(hashHex).replace("=", "-") // shorten from 40 to 28 chars
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function hexToBase64(hexStr) {
|
|
12
|
-
return btoa(
|
|
13
|
-
[...hexStr].reduce(
|
|
14
|
-
(acc, _, i) =>
|
|
15
|
-
(acc += !((i - 1) & 1)
|
|
16
|
-
? String.fromCharCode(parseInt(hexStr.substring(i - 1, i + 1), 16))
|
|
17
|
-
: ""),
|
|
18
|
-
""
|
|
19
|
-
)
|
|
20
|
-
)
|
|
21
|
-
}
|
package/src/helpers.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
this is a helper file with a collection of functions to be used in backend and client side code. Here is a list.
|
|
3
|
-
|
|
4
|
-
1. isNumeric(n)
|
|
5
|
-
2. strictNumeric(n)
|
|
6
|
-
2. convertUnits
|
|
7
|
-
3. TODO - move computepercentile, roundValue, etc here?
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// checks whether given argument n is Numeric, with option to cast from string
|
|
11
|
-
export function isNumeric(n) {
|
|
12
|
-
const v = typeof n != "string" || n === "" ? n : Number(n)
|
|
13
|
-
const f = parseFloat(n)
|
|
14
|
-
return !isNaN(f) && Number.isFinite(v) && v === f
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// like isNumeric but does not cast from string
|
|
18
|
-
export function isStrictNumeric(n) {
|
|
19
|
-
return typeof n === "number" && Number.isFinite(n)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// converts a value from a unit to another unit
|
|
23
|
-
export function convertUnits(v, fromUnit, toUnit, scaleFactor, compact) {
|
|
24
|
-
// do floor() on toUnit
|
|
25
|
-
// do ceil() on fromUnit, in case v is decimal (from violin range selection) and to keep showing integer fromUnit
|
|
26
|
-
if (scaleFactor >= 1) {
|
|
27
|
-
const toUnitV = Math.floor(v * scaleFactor)
|
|
28
|
-
if (compact) return `${toUnitV}${toUnit.charAt(0)}`
|
|
29
|
-
return `${toUnitV} ${toUnitV > 1 ? toUnit + "s" : ""}`
|
|
30
|
-
}
|
|
31
|
-
const toUnitV = Math.floor(v * scaleFactor)
|
|
32
|
-
const fromUnitV = Math.ceil(v % (1 / scaleFactor))
|
|
33
|
-
|
|
34
|
-
if (fromUnitV == 0) {
|
|
35
|
-
if (compact) return `${toUnitV}${toUnit.charAt(0)}`
|
|
36
|
-
return `${toUnitV} ${toUnitV > 1 ? toUnit + "s" : ""}`
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (compact)
|
|
40
|
-
return `${toUnitV}${toUnit.charAt(0)}${fromUnitV}${fromUnit.charAt(0)}`
|
|
41
|
-
return `${toUnitV} ${toUnitV > 1 ? toUnit + "s" : toUnit} ${fromUnitV} ${
|
|
42
|
-
fromUnitV > 1 ? fromUnit + "s" : fromUnit
|
|
43
|
-
}`
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function deepEqual(x, y) {
|
|
47
|
-
if (x === y) {
|
|
48
|
-
return true
|
|
49
|
-
} else if (
|
|
50
|
-
typeof x == "object" &&
|
|
51
|
-
x != null &&
|
|
52
|
-
typeof y == "object" &&
|
|
53
|
-
y != null
|
|
54
|
-
) {
|
|
55
|
-
if (Object.keys(x).length != Object.keys(y).length) {
|
|
56
|
-
return false
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for (var prop in x) {
|
|
60
|
-
if (y.hasOwnProperty(prop)) {
|
|
61
|
-
if (!deepEqual(x[prop], y[prop])) return false
|
|
62
|
-
} else {
|
|
63
|
-
return false
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return true
|
|
67
|
-
} else return false
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function deepFreeze(obj) {
|
|
71
|
-
Object.freeze(obj)
|
|
72
|
-
// not using for..in loop, in order to not descend into inherited props/methods
|
|
73
|
-
for (const value of Object.values(obj)) {
|
|
74
|
-
if (value !== null && typeof value == "object") deepFreeze(value)
|
|
75
|
-
}
|
|
76
|
-
return obj
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export class CustomError extends Error {
|
|
80
|
-
level = "" // '' | 'warn'
|
|
81
|
-
|
|
82
|
-
constructor(message, opts = {}) {
|
|
83
|
-
super(message)
|
|
84
|
-
if (opts.name) this.name = opts.name
|
|
85
|
-
if (opts.code) this.code = opts.code
|
|
86
|
-
if (opts.level) this.level = opts.level
|
|
87
|
-
}
|
|
88
|
-
}
|