@sjcrh/proteinpaint-shared 2.186.0 → 2.188.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.
Files changed (117) hide show
  1. package/README.md +10 -2
  2. package/constants/AiHisto.ts +27 -0
  3. package/constants/README.md +11 -0
  4. package/devTs.ts +3 -0
  5. package/dist/constants/AiHisto.d.ts +23 -0
  6. package/dist/constants/AiHisto.js +31 -0
  7. package/dist/constants/AiHisto.js.map +7 -0
  8. package/dist/src/aiHisto.d.ts +5 -0
  9. package/dist/src/aiHisto.js +15 -0
  10. package/dist/src/aiHisto.js.map +7 -0
  11. package/dist/src/bulk.cnv.js +83 -0
  12. package/dist/src/bulk.cnv.js.map +7 -0
  13. package/dist/src/bulk.del.js +119 -0
  14. package/dist/src/bulk.del.js.map +7 -0
  15. package/dist/src/bulk.itd.js +119 -0
  16. package/dist/src/bulk.itd.js.map +7 -0
  17. package/dist/src/bulk.js +183 -0
  18. package/dist/src/bulk.js.map +7 -0
  19. package/dist/src/bulk.snv.js +175 -0
  20. package/dist/src/bulk.snv.js.map +7 -0
  21. package/dist/src/bulk.sv.js +266 -0
  22. package/dist/src/bulk.sv.js.map +7 -0
  23. package/dist/src/bulk.svjson.js +151 -0
  24. package/dist/src/bulk.svjson.js.map +7 -0
  25. package/dist/src/bulk.trunc.js +122 -0
  26. package/dist/src/bulk.trunc.js.map +7 -0
  27. package/dist/src/clustering.js +71 -0
  28. package/dist/src/clustering.js.map +7 -0
  29. package/dist/src/common.js +1302 -0
  30. package/dist/src/common.js.map +7 -0
  31. package/dist/src/compute.percentile.js +10 -0
  32. package/dist/src/compute.percentile.js.map +7 -0
  33. package/dist/src/doc.d.ts +7 -0
  34. package/dist/src/doc.js +10 -0
  35. package/dist/src/doc.js.map +7 -0
  36. package/dist/src/fetch-helpers.js +177 -0
  37. package/dist/src/fetch-helpers.js.map +7 -0
  38. package/dist/src/fileSize.js +10 -0
  39. package/dist/src/fileSize.js.map +7 -0
  40. package/dist/src/filter.d.ts +62 -0
  41. package/dist/src/filter.js +194 -0
  42. package/dist/src/filter.js.map +7 -0
  43. package/dist/src/hash.js +20 -0
  44. package/dist/src/hash.js.map +7 -0
  45. package/dist/src/helpers.js +66 -0
  46. package/dist/src/helpers.js.map +7 -0
  47. package/dist/src/index.d.ts +26 -0
  48. package/dist/src/index.js +27 -0
  49. package/dist/src/index.js.map +7 -0
  50. package/dist/src/joinUrl.d.ts +1 -0
  51. package/dist/src/joinUrl.js +17 -0
  52. package/dist/src/joinUrl.js.map +7 -0
  53. package/dist/src/mds3tk.js +64 -0
  54. package/dist/src/mds3tk.js.map +7 -0
  55. package/dist/src/roundValue.js +57 -0
  56. package/dist/src/roundValue.js.map +7 -0
  57. package/dist/src/termdb.bins.js +272 -0
  58. package/dist/src/termdb.bins.js.map +7 -0
  59. package/dist/src/termdb.initbinconfig.js +79 -0
  60. package/dist/src/termdb.initbinconfig.js.map +7 -0
  61. package/dist/src/termdb.usecase.js +239 -0
  62. package/dist/src/termdb.usecase.js.map +7 -0
  63. package/dist/src/terms.d.ts +83 -0
  64. package/dist/src/terms.js +327 -0
  65. package/dist/src/terms.js.map +7 -0
  66. package/dist/src/time.d.ts +9 -0
  67. package/dist/src/time.js +23 -0
  68. package/dist/src/time.js.map +7 -0
  69. package/dist/src/tree.js +82 -0
  70. package/dist/src/tree.js.map +7 -0
  71. package/dist/src/urljson.d.ts +8 -0
  72. package/dist/src/urljson.js +31 -0
  73. package/dist/src/urljson.js.map +7 -0
  74. package/dist/src/vcf.ann.js +56 -0
  75. package/dist/src/vcf.ann.js.map +7 -0
  76. package/dist/src/vcf.csq.js +82 -0
  77. package/dist/src/vcf.csq.js.map +7 -0
  78. package/dist/src/vcf.info.js +40 -0
  79. package/dist/src/vcf.info.js.map +7 -0
  80. package/dist/src/vcf.js +439 -0
  81. package/dist/src/vcf.js.map +7 -0
  82. package/dist/src/vcf.type.js +17 -0
  83. package/dist/src/vcf.type.js.map +7 -0
  84. package/package.json +20 -11
  85. package/src/bulk.cnv.js +0 -86
  86. package/src/bulk.del.js +0 -124
  87. package/src/bulk.itd.js +0 -123
  88. package/src/bulk.js +0 -197
  89. package/src/bulk.snv.js +0 -271
  90. package/src/bulk.sv.js +0 -276
  91. package/src/bulk.svjson.js +0 -164
  92. package/src/bulk.trunc.js +0 -132
  93. package/src/clustering.js +0 -66
  94. package/src/common.js +0 -1608
  95. package/src/compute.percentile.js +0 -11
  96. package/src/doc.js +0 -6
  97. package/src/fetch-helpers.js +0 -323
  98. package/src/fileSize.js +0 -6
  99. package/src/filter.js +0 -221
  100. package/src/hash.js +0 -21
  101. package/src/helpers.js +0 -88
  102. package/src/index.js +0 -26
  103. package/src/joinUrl.js +0 -14
  104. package/src/mds3tk.js +0 -100
  105. package/src/roundValue.js +0 -94
  106. package/src/termdb.bins.js +0 -456
  107. package/src/termdb.initbinconfig.js +0 -130
  108. package/src/termdb.usecase.js +0 -317
  109. package/src/terms.js +0 -341
  110. package/src/time.js +0 -22
  111. package/src/tree.js +0 -138
  112. package/src/urljson.js +0 -41
  113. package/src/vcf.ann.js +0 -62
  114. package/src/vcf.csq.js +0 -153
  115. package/src/vcf.info.js +0 -50
  116. package/src/vcf.js +0 -654
  117. 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
@@ -1,6 +0,0 @@
1
- const test = {}
2
- function doc(opts) {
3
- if (opts.type in test) throw `test['${opts.type}'] already exists`
4
- test[opts.type] = opts.test
5
- }
6
- export { doc, test }
@@ -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
@@ -1,6 +0,0 @@
1
- export function fileSize(v) {
2
- if (v > 1e9) return (v / 1e9).toFixed(2) + " GB"
3
- if (v > 1e6) return (v / 1e6).toFixed(2) + " MB"
4
- if (v > 1e3) return (v / 1e3).toFixed(2) + " KB"
5
- return v + " Bytes"
6
- }
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
- }