@sjcrh/proteinpaint-shared 2.175.0 → 2.177.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 +1 -1
- package/src/common.js +7 -6
- package/src/fetch-helpers.js +71 -54
- package/src/helpers.js +9 -0
- package/src/termdb.usecase.js +1 -17
- package/src/terms.js +3 -1
package/package.json
CHANGED
package/src/common.js
CHANGED
|
@@ -17,21 +17,22 @@ import { getWrappedTvslst } from './filter.js'
|
|
|
17
17
|
// part of 'common' argument to exported dataset js function, at server runtime
|
|
18
18
|
export const TermTypeGroups = {
|
|
19
19
|
DICTIONARY_VARIABLES: 'Dictionary Variables',
|
|
20
|
-
MUTATION_CNV_FUSION: 'Mutation/CNV/Fusion',
|
|
21
|
-
VARIANT_GENOTYPE: 'Variant Genotype',
|
|
22
20
|
DNA_METHYLATION: 'DNA Methylation',
|
|
23
21
|
GENE_DEPENDENCY: 'Gene Dependency',
|
|
24
22
|
GENE_EXPRESSION: 'Gene Expression',
|
|
25
|
-
PROTEIN_EXPRESSION: 'Protein Expression',
|
|
26
|
-
SPLICE_JUNCTION: 'Splice Junction',
|
|
27
|
-
METABOLITE_INTENSITY: 'Metabolite Intensity',
|
|
28
23
|
GSEA: 'GSEA',
|
|
24
|
+
METABOLITE_INTENSITY: 'Metabolite Intensity',
|
|
25
|
+
MUTATION_CNV_FUSION: 'Mutation/CNV/Fusion',
|
|
29
26
|
MUTATION_SIGNATURE: 'Mutation Signature',
|
|
27
|
+
PROTEIN_EXPRESSION: 'Protein Expression',
|
|
28
|
+
SINGLECELL_CELLTYPE: 'Single-cell Cell Type',
|
|
30
29
|
SNP: 'SNP Genotype',
|
|
31
30
|
SNP_LIST: 'SNP List',
|
|
32
31
|
SNP_LOCUS: 'SNP Locus',
|
|
32
|
+
SPLICE_JUNCTION: 'Splice Junction',
|
|
33
33
|
SSGSEA: 'Geneset Expression',
|
|
34
|
-
TERM_COLLECTION: 'Term Collection'
|
|
34
|
+
TERM_COLLECTION: 'Term Collection',
|
|
35
|
+
VARIANT_GENOTYPE: 'Variant Genotype'
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export const defaultcolor = rgb('#8AB1D4').darker()
|
package/src/fetch-helpers.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hash } from './hash.js'
|
|
2
2
|
import { encode } from './urljson.js'
|
|
3
|
+
import { deepFreeze } from './helpers.js'
|
|
3
4
|
|
|
4
5
|
/*
|
|
5
6
|
ezFetch()
|
|
@@ -165,17 +166,15 @@ async function processNDJSON_nestedKey(r) {
|
|
|
165
166
|
return rootObj
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
// key: request object reference or
|
|
169
|
-
// value:
|
|
169
|
+
// key: request object reference or computed string dataName
|
|
170
|
+
// value: {
|
|
171
|
+
// response: fetch promise or response,
|
|
172
|
+
// exp: expiration timestamp
|
|
173
|
+
// }
|
|
170
174
|
const dataCache = new Map()
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// when caching by string dataName, track entries to manage the cache size
|
|
175
|
-
const cachedDataNames = []
|
|
176
|
-
// maximum number of cached dataNames, oldest will be deleted if 1000 is exceeded
|
|
177
|
-
const maxNumOfDataKeys = 360
|
|
178
|
-
|
|
175
|
+
// maximum number of cached dataNames, oldest will be deleted if this is exceeded
|
|
176
|
+
const maxNumOfDataKeys = 10
|
|
177
|
+
const cacheLifetime = 1000 * 60 * 5
|
|
179
178
|
/*
|
|
180
179
|
memFetch()
|
|
181
180
|
- fetch wrapper that saves cached responses into memory and recovers them for matching subsequent requests
|
|
@@ -188,65 +187,83 @@ const maxNumOfDataKeys = 360
|
|
|
188
187
|
url
|
|
189
188
|
init{headers?, body?}
|
|
190
189
|
- first two arguments are same as native fetch
|
|
190
|
+
- when passing opts.client, may include other applicable options inside the init{} object, such as retry
|
|
191
191
|
|
|
192
|
-
opts{
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
opts{client}
|
|
193
|
+
|
|
194
|
+
client: use this http client instead of native fetch
|
|
195
|
+
- since fetch-helpers is shared between server and frontend workspaces,
|
|
196
|
+
cannot directly import non-native modules at the beginning of this code file
|
|
197
|
+
- for server side usage, client may be `xfetch()`, `ky` or other libraries
|
|
196
198
|
*/
|
|
197
199
|
export async function memFetch(url, init, opts = {}) {
|
|
198
200
|
if (typeof init.body === 'object') init.body = JSON.stringify(init.body)
|
|
199
201
|
const dataKey = opts.q || (await getDataName(url, init))
|
|
200
|
-
|
|
202
|
+
const { response, exp } = dataCache.get(dataKey) || {}
|
|
203
|
+
const now = Date.now()
|
|
204
|
+
let result = response // either a Promise or actual data
|
|
201
205
|
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
if (result) {
|
|
207
|
+
// extend the expiration, since exp is more about managing the cache size
|
|
208
|
+
// and not the validity of the cached response. A response for the current
|
|
209
|
+
// dataName req.url + body + headers is technically valid until a new data version
|
|
210
|
+
// gets published.
|
|
211
|
+
dataCache.set(dataKey, { response, exp: now + cacheLifetime })
|
|
212
|
+
return result
|
|
213
|
+
} else {
|
|
208
214
|
try {
|
|
209
|
-
// do not await so that this same promise may be reused
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
215
|
+
// IMPORTANT: do not await so that this same promise may be reused
|
|
216
|
+
// by subsequent requests with the same dataKey
|
|
217
|
+
result = opts.client
|
|
218
|
+
? opts.client(url, init, Object.assign(opts, { client: undefined })).then(response => {
|
|
219
|
+
// replace the cached promise result with the actual data,
|
|
220
|
+
// since persisting a cached promise for a long time is likely not best practice
|
|
221
|
+
dataCache.set(dataKey, { response, exp: Date.now() + cacheLifetime })
|
|
222
|
+
return response
|
|
223
|
+
})
|
|
224
|
+
: fetch(url, init).then(async r => {
|
|
225
|
+
const response = await processResponse(r)
|
|
226
|
+
if (!r.ok) {
|
|
227
|
+
console.trace(response)
|
|
228
|
+
throw (
|
|
229
|
+
'memFetch error ' +
|
|
230
|
+
r.status +
|
|
231
|
+
': ' +
|
|
232
|
+
(typeof response == 'object' ? response.message || response.error : response)
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
// replace the cached promise result with the actual data,
|
|
236
|
+
// since persisting a cached promise for a long time is likely not best practice
|
|
237
|
+
dataCache.set(dataKey, { response: deepFreeze(response), exp: Date.now() + cacheLifetime })
|
|
238
|
+
return response
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
dataCache.set(dataKey, { response: result, exp: Date.now() + cacheLifetime })
|
|
242
|
+
manageCacheSize(now)
|
|
243
|
+
return result
|
|
229
244
|
} catch (e) {
|
|
230
|
-
delete
|
|
245
|
+
// delete this cache only if it is a promise;
|
|
246
|
+
// do not delete a valid resolved data cache
|
|
247
|
+
if (dataCache.get(dataKey) instanceof Promise) delete dataCache.delete(dataKey)
|
|
231
248
|
throw e
|
|
232
249
|
}
|
|
233
250
|
}
|
|
234
|
-
if (typeof dataKey === 'string') manageCacheSize(dataKey)
|
|
235
|
-
return result
|
|
236
251
|
}
|
|
237
252
|
|
|
238
253
|
export function deleteCache(key) {
|
|
239
|
-
|
|
254
|
+
dataCache.delete(key)
|
|
240
255
|
}
|
|
241
256
|
|
|
242
|
-
export function manageCacheSize(
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
export function manageCacheSize(_now) {
|
|
258
|
+
const now = _now || Date.now()
|
|
259
|
+
const keyExp = []
|
|
260
|
+
for (const [key, result] of dataCache.entries()) {
|
|
261
|
+
if (result.exp < now) dataCache.delete(key)
|
|
262
|
+
else keyExp.push({ key, exp: result.exp })
|
|
263
|
+
}
|
|
264
|
+
if (dataCache.size > maxNumOfDataKeys) {
|
|
265
|
+
const oldestEntries = keyExp.sort((a, b) => a.exp - b.exp).slice(maxNumOfDataKeys)
|
|
266
|
+
for (const entry of oldestEntries) dataCache.delete(entry.key)
|
|
250
267
|
}
|
|
251
268
|
}
|
|
252
269
|
|
|
@@ -261,7 +278,7 @@ export async function getDataName(url, init) {
|
|
|
261
278
|
}
|
|
262
279
|
|
|
263
280
|
//
|
|
264
|
-
export function
|
|
281
|
+
export function clearMemFetchDataCache(opts = {}) {
|
|
265
282
|
if (!opts.serverData) {
|
|
266
283
|
dataCache.clear()
|
|
267
284
|
return
|
package/src/helpers.js
CHANGED
|
@@ -59,6 +59,15 @@ export function deepEqual(x, y) {
|
|
|
59
59
|
} else return false
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
export function deepFreeze(obj) {
|
|
63
|
+
Object.freeze(obj)
|
|
64
|
+
// not using for..in loop, in order to not descend into inherited props/methods
|
|
65
|
+
for (const value of Object.values(obj)) {
|
|
66
|
+
if (value !== null && typeof value == 'object') deepFreeze(value)
|
|
67
|
+
}
|
|
68
|
+
return obj
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
export class CustomError extends Error {
|
|
63
72
|
level = '' // '' | 'warn'
|
|
64
73
|
|
package/src/termdb.usecase.js
CHANGED
|
@@ -113,23 +113,7 @@ export function isUsableTerm(term, _usecase, termdbConfig, ds) {
|
|
|
113
113
|
}
|
|
114
114
|
return uses
|
|
115
115
|
case 'runChart2':
|
|
116
|
-
if (usecase.detail == 'date') {
|
|
117
|
-
if (term.type == 'date') {
|
|
118
|
-
uses.add('plot')
|
|
119
|
-
}
|
|
120
|
-
if (child_types.includes('date')) uses.add('branch')
|
|
121
|
-
} else if (usecase.detail == 'numeric') {
|
|
122
|
-
if (isNumericTerm(term) && term.type != 'date') {
|
|
123
|
-
uses.add('plot')
|
|
124
|
-
}
|
|
125
|
-
if (hasNumericChild(child_types)) uses.add('branch')
|
|
126
|
-
} else {
|
|
127
|
-
if (graphableTypes.has(term.type)) uses.add('plot')
|
|
128
|
-
if (!term.isleaf) uses.add('branch')
|
|
129
|
-
}
|
|
130
|
-
return uses
|
|
131
|
-
case 'frequencyChart':
|
|
132
|
-
if (usecase.detail == 'term') {
|
|
116
|
+
if (usecase.detail == 'date' || usecase.detail == 'xtw') {
|
|
133
117
|
if (term.type == 'date') {
|
|
134
118
|
uses.add('plot')
|
|
135
119
|
}
|
package/src/terms.js
CHANGED
|
@@ -70,7 +70,8 @@ export const typeGroup = {
|
|
|
70
70
|
[TermTypes.GENE_EXPRESSION]: TermTypeGroups.GENE_EXPRESSION,
|
|
71
71
|
[TermTypes.SSGSEA]: TermTypeGroups.SSGSEA,
|
|
72
72
|
[TermTypes.METABOLITE_INTENSITY]: TermTypeGroups.METABOLITE_INTENSITY,
|
|
73
|
-
[TermTypes.TERM_COLLECTION]: TermTypeGroups.TERM_COLLECTION
|
|
73
|
+
[TermTypes.TERM_COLLECTION]: TermTypeGroups.TERM_COLLECTION,
|
|
74
|
+
[TermTypes.SINGLECELL_CELLTYPE]: TermTypeGroups.SINGLECELL_CELLTYPE
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
const nonDictTypes = new Set([
|
|
@@ -196,6 +197,7 @@ export function getParentType(types, ds) {
|
|
|
196
197
|
if (!ids || ids.length == 0) return null
|
|
197
198
|
for (const id of ids) {
|
|
198
199
|
const typeObj = ds.cohort.termdb.sampleTypes[id]
|
|
200
|
+
if (!typeObj) continue
|
|
199
201
|
if (typeObj.parent_id == null) return id //this is the root type
|
|
200
202
|
//if my parent is in the list, then I am not the parent
|
|
201
203
|
if (ids.includes(typeObj.parent_id)) continue
|