@apollo-annotation/common 0.1.17 → 0.1.19

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": "@apollo-annotation/common",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/GMOD/Apollo3.git",
@@ -11,9 +11,9 @@
11
11
  "build": "tsc"
12
12
  },
13
13
  "dependencies": {
14
- "@apollo-annotation/schemas": "^0.1.17",
14
+ "@apollo-annotation/schemas": "^0.1.19",
15
15
  "@gmod/gff": "1.2.0",
16
- "@jbrowse/core": "^2.7.0",
16
+ "@jbrowse/core": "^2.13.1",
17
17
  "bson-objectid": "^2.0.4",
18
18
  "tslib": "^2.3.1"
19
19
  },
@@ -30,12 +30,12 @@
30
30
  "tss-react": "^4.6.1"
31
31
  },
32
32
  "devDependencies": {
33
- "@apollo-annotation/mst": "^0.1.17",
33
+ "@apollo-annotation/mst": "^0.1.19",
34
34
  "@nestjs/common": "^10.1.0",
35
35
  "@nestjs/core": "^10.1.0",
36
36
  "@types/node": "^18.14.2",
37
37
  "mongoose": "^6.12.0",
38
- "typescript": "^5.1.6"
38
+ "typescript": "^5.5.3"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public"
@@ -1,16 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4
- /* eslint-disable @typescript-eslint/no-unsafe-call */
5
- /* eslint-disable @typescript-eslint/no-unsafe-argument */
6
- /* eslint-disable @typescript-eslint/restrict-template-expressions */
7
- import type { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
8
- import { FileDocument, RefSeqDocument } from '@apollo-annotation/schemas'
9
- import { GFF3Feature } from '@gmod/gff'
10
- import ObjectID from 'bson-objectid'
11
-
12
2
  import { Change, ChangeOptions, SerializedChange, isChange } from './Change'
13
- import { ServerDataStore } from './Operation'
14
3
 
15
4
  export interface SerializedAssemblySpecificChange extends SerializedChange {
16
5
  assembly: string
@@ -32,437 +21,4 @@ export abstract class AssemblySpecificChange extends Change {
32
21
  super(json, options)
33
22
  this.assembly = json.assembly
34
23
  }
35
-
36
- async addRefSeqIntoDb(
37
- fileDoc: FileDocument,
38
- assembly: string,
39
- backend: ServerDataStore,
40
- ) {
41
- const { logger } = this
42
- const { filesService, refSeqChunkModel, refSeqModel, user } = backend
43
- const { CHUNK_SIZE } = process.env
44
- const customChunkSize = CHUNK_SIZE && Number(CHUNK_SIZE)
45
- let chunkIndex = 0
46
- let refSeqLen = 0
47
- let refSeqDoc: RefSeqDocument | undefined
48
- let fastaInfoStarted = fileDoc.type !== 'text/x-gff3'
49
-
50
- // Read data from compressed file and parse the content
51
- const sequenceStream = filesService.getFileStream(fileDoc)
52
- let sequenceBuffer = ''
53
- let incompleteLine = ''
54
- let lastLineIsIncomplete = true
55
- let parsingStarted = false
56
- logger.debug?.('starting sequence stream')
57
- for await (const data of sequenceStream) {
58
- const chunk = data.toString()
59
- lastLineIsIncomplete = !chunk.endsWith('\n')
60
- // chunk is small enough that you can split the whole thing into lines without having to make it into smaller chunks first.
61
- const lines = chunk.split(/\r?\n/)
62
- if (incompleteLine) {
63
- lines[0] = `${incompleteLine}${lines[0]}`
64
- incompleteLine = ''
65
- }
66
- if (lastLineIsIncomplete) {
67
- incompleteLine = lines.pop() || ''
68
- }
69
- for await (const line of lines) {
70
- // In case of GFF3 file we start to read sequence after '##FASTA' is found
71
- if (!fastaInfoStarted) {
72
- if (line.trim() === '##FASTA') {
73
- fastaInfoStarted = true
74
- }
75
- continue
76
- }
77
- const refSeqInfoLine = /^>\s*(\S+)\s*(.*)/.exec(line)
78
- // Add new ref sequence info if we are reference seq info line
79
- if (refSeqInfoLine) {
80
- parsingStarted = true
81
- logger.debug?.(
82
- `Reference sequence information line "${refSeqInfoLine}"`,
83
- )
84
-
85
- // If there is sequence from previous reference sequence then we need to add it to previous ref seq
86
- if (sequenceBuffer !== '') {
87
- if (!refSeqDoc) {
88
- throw new Error('No refSeq document found')
89
- }
90
- refSeqLen += sequenceBuffer.length
91
- logger.debug?.(
92
- `Creating refSeq chunk number ${chunkIndex} of "${refSeqDoc._id}"`,
93
- )
94
- // We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
95
- await refSeqChunkModel.create([
96
- {
97
- refSeq: refSeqDoc._id,
98
- n: chunkIndex,
99
- sequence: sequenceBuffer,
100
- user,
101
- status: -1,
102
- },
103
- ])
104
- sequenceBuffer = ''
105
- }
106
- await refSeqDoc?.updateOne({ length: refSeqLen })
107
- // await refSeqDoc?.updateOne({ length: refSeqLen }, { session })
108
- refSeqLen = 0
109
- chunkIndex = 0
110
-
111
- const name = refSeqInfoLine[1].trim()
112
- const description = refSeqInfoLine[2] ? refSeqInfoLine[2].trim() : ''
113
-
114
- // We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
115
- const [newRefSeqDoc] = await refSeqModel.create([
116
- {
117
- name,
118
- description,
119
- assembly,
120
- length: 0,
121
- ...(customChunkSize ? { chunkSize: customChunkSize } : null),
122
- user,
123
- status: -1,
124
- },
125
- ])
126
- logger.debug?.(
127
- `Added new refSeq "${name}", desc "${description}", docId "${newRefSeqDoc._id}"`,
128
- )
129
- refSeqDoc = newRefSeqDoc
130
- } else if (/\S/.test(line)) {
131
- if (!refSeqDoc) {
132
- throw new Error('No refSeq document found')
133
- }
134
- const { _id, chunkSize } = refSeqDoc
135
- sequenceBuffer += line.replaceAll(/\s/g, '')
136
- // If sequence block > chunk size then save chunk into Mongo
137
- while (sequenceBuffer.length >= chunkSize) {
138
- const sequence = sequenceBuffer.slice(0, chunkSize)
139
- refSeqLen += sequence.length
140
- logger.debug?.(
141
- `Creating refSeq chunk number ${chunkIndex} of "${_id}"`,
142
- )
143
- // We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
144
- await refSeqChunkModel.create([
145
- { refSeq: _id, n: chunkIndex, sequence, user, status: -1 },
146
- ])
147
- chunkIndex++
148
- // Set remaining sequence
149
- sequenceBuffer = sequenceBuffer.slice(chunkSize)
150
- logger.debug?.(`Remaining sequence: "${sequenceBuffer}"`)
151
- }
152
- }
153
- }
154
- }
155
- if (!parsingStarted) {
156
- throw new Error('No reference sequences found in file')
157
- }
158
-
159
- if (sequenceBuffer || lastLineIsIncomplete) {
160
- if (!refSeqDoc) {
161
- throw new Error('No refSeq document found')
162
- }
163
- // If the file did not end with line break so the last line is incomplete
164
- if (lastLineIsIncomplete) {
165
- sequenceBuffer += incompleteLine
166
- }
167
- refSeqLen += sequenceBuffer.length
168
- logger.verbose?.(
169
- `*** Add the very last chunk to ref seq ("${refSeqDoc._id}", index ${chunkIndex} and total length for ref seq is ${refSeqLen}): "${sequenceBuffer}"`,
170
- )
171
- logger.debug?.(
172
- `Creating refSeq chunk number ${chunkIndex} of "${refSeqDoc._id}"`,
173
- )
174
- // We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
175
- await refSeqChunkModel.create([
176
- {
177
- refSeq: refSeqDoc._id,
178
- n: chunkIndex,
179
- sequence: sequenceBuffer,
180
- user,
181
- status: -1,
182
- },
183
- ])
184
- await refSeqDoc.updateOne({ length: refSeqLen })
185
- }
186
- }
187
-
188
- private refSeqCache = new Map<string, RefSeqDocument>()
189
-
190
- async removeExistingFeatures(backend: ServerDataStore) {
191
- const { featureModel, refSeqModel } = backend
192
- const { assembly, logger } = this
193
- logger.debug?.(`Removing existing features for assembly = ${assembly}`)
194
-
195
- const refSeqs: RefSeqDocument[] = await refSeqModel
196
- .find({ assembly })
197
- .exec()
198
-
199
- for (const refSeq of refSeqs) {
200
- await featureModel.deleteMany({ refSeq: refSeq._id })
201
- }
202
- }
203
-
204
- async addFeatureIntoDb(gff3Feature: GFF3Feature, backend: ServerDataStore) {
205
- const { featureModel, refSeqModel, user } = backend
206
- const { assembly, logger, refSeqCache } = this
207
-
208
- const [{ seq_id: refName }] = gff3Feature
209
- if (!refName) {
210
- throw new Error(
211
- `Valid seq_id not found in feature ${JSON.stringify(gff3Feature)}`,
212
- )
213
- }
214
- let refSeqDoc = refSeqCache.get(refName)
215
- if (!refSeqDoc) {
216
- refSeqDoc =
217
- (await refSeqModel.findOne({ assembly, name: refName }).exec()) ??
218
- undefined
219
- if (refSeqDoc) {
220
- refSeqCache.set(refName, refSeqDoc)
221
- }
222
- }
223
- if (!refSeqDoc) {
224
- throw new Error(
225
- `RefSeq was not found by assembly "${assembly}" and seq_id "${refName}" not found`,
226
- )
227
- }
228
- // Let's add featureId to parent feature
229
- const featureIds: string[] = []
230
-
231
- const newFeature = createFeature(gff3Feature, refSeqDoc._id, featureIds)
232
- logger.debug?.(`So far feature ids are: ${featureIds.toString()}`)
233
- // Add value to gffId
234
- newFeature.attributes?._id
235
- ? (newFeature.gffId = newFeature.attributes?._id.toString())
236
- : (newFeature.gffId = newFeature._id)
237
- logger.debug?.(
238
- `********************* Assembly specific change create ${JSON.stringify(
239
- newFeature,
240
- )}`,
241
- )
242
-
243
- // Add into Mongo
244
- // We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
245
- const [newFeatureDoc] = await featureModel.create([
246
- { allIds: featureIds, ...newFeature, user, status: -1 },
247
- ])
248
- logger.verbose?.(`Added docId "${newFeatureDoc._id}"`)
249
- }
250
- }
251
-
252
- function createFeature(
253
- gff3Feature: GFF3Feature,
254
- refSeq: string,
255
- featureIds?: string[],
256
- ): AnnotationFeatureSnapshot {
257
- const [firstFeature] = gff3Feature
258
- const {
259
- attributes,
260
- child_features: childFeatures,
261
- end,
262
- phase,
263
- score,
264
- seq_id: refName,
265
- source,
266
- start,
267
- strand,
268
- type,
269
- } = firstFeature
270
- if (!refName) {
271
- throw new Error(
272
- `feature does not have seq_id: ${JSON.stringify(firstFeature)}`,
273
- )
274
- }
275
- if (!type) {
276
- throw new Error(
277
- `feature does not have type: ${JSON.stringify(firstFeature)}`,
278
- )
279
- }
280
- if (start === null) {
281
- throw new Error(
282
- `feature does not have start: ${JSON.stringify(firstFeature)}`,
283
- )
284
- }
285
- if (end === null) {
286
- throw new Error(
287
- `feature does not have end: ${JSON.stringify(firstFeature)}`,
288
- )
289
- }
290
- const feature: AnnotationFeatureSnapshot = {
291
- _id: new ObjectID().toHexString(),
292
- gffId: '',
293
- refSeq,
294
- type,
295
- start: start - 1,
296
- end,
297
- }
298
- if (gff3Feature.length > 1) {
299
- const lastEnd = Math.max(
300
- ...gff3Feature.map((f) => {
301
- if (f.end === null) {
302
- throw new Error(`feature does not have end: ${JSON.stringify(f)}`)
303
- }
304
- return f.end
305
- }),
306
- )
307
- feature.end = lastEnd
308
- feature.discontinuousLocations = gff3Feature.map((f) => {
309
- const { end: subEnd, phase: locationPhase, start: subStart } = f
310
- if (subStart === null || subEnd === null) {
311
- throw new Error(
312
- `feature does not have start and/or end: ${JSON.stringify(f)}`,
313
- )
314
- }
315
- let parsedPhase: 0 | 1 | 2 | undefined
316
- if (locationPhase) {
317
- switch (locationPhase) {
318
- case '0': {
319
- parsedPhase = 0
320
-
321
- break
322
- }
323
- case '1': {
324
- parsedPhase = 1
325
-
326
- break
327
- }
328
- case '2': {
329
- parsedPhase = 2
330
-
331
- break
332
- }
333
- default: {
334
- throw new Error(`Unknown phase: "${locationPhase}"`)
335
- }
336
- }
337
- }
338
- return { start: subStart - 1, end: subEnd, phase: parsedPhase }
339
- })
340
- }
341
- if (strand) {
342
- if (strand === '+') {
343
- feature.strand = 1
344
- } else if (strand === '-') {
345
- feature.strand = -1
346
- } else {
347
- throw new Error(`Unknown strand: "${strand}"`)
348
- }
349
- }
350
- if (score !== null) {
351
- feature.score = score
352
- }
353
- if (phase) {
354
- switch (phase) {
355
- case '0': {
356
- feature.phase = 0
357
-
358
- break
359
- }
360
- case '1': {
361
- feature.phase = 1
362
-
363
- break
364
- }
365
- case '2': {
366
- feature.phase = 2
367
-
368
- break
369
- }
370
- default: {
371
- throw new Error(`Unknown phase: "${phase}"`)
372
- }
373
- }
374
- }
375
- if (featureIds) {
376
- featureIds.push(feature._id)
377
- }
378
-
379
- if (childFeatures?.length) {
380
- const children: Record<string, AnnotationFeatureSnapshot> = {}
381
- for (const childFeature of childFeatures) {
382
- const child = createFeature(childFeature, refSeq, featureIds)
383
- children[child._id] = child
384
- // Add value to gffId
385
- child.attributes?._id
386
- ? (child.gffId = child.attributes?._id.toString())
387
- : (child.gffId = child._id)
388
- }
389
- feature.children = children
390
- }
391
- if (source ?? attributes) {
392
- const attrs: Record<string, string[]> = {}
393
- if (source) {
394
- attrs.source = [source]
395
- }
396
- if (attributes) {
397
- for (const [key, val] of Object.entries(attributes)) {
398
- if (val) {
399
- const newKey = key.toLowerCase()
400
- if (newKey !== 'parent') {
401
- // attrs[key.toLowerCase()] = val
402
- switch (key) {
403
- case 'ID': {
404
- attrs._id = val
405
- break
406
- }
407
- case 'Name': {
408
- attrs.gff_name = val
409
- break
410
- }
411
- case 'Alias': {
412
- attrs.gff_alias = val
413
- break
414
- }
415
- case 'Target': {
416
- attrs.gff_target = val
417
- break
418
- }
419
- case 'Gap': {
420
- attrs.gff_gap = val
421
- break
422
- }
423
- case 'Derives_from': {
424
- attrs.gff_derives_from = val
425
- break
426
- }
427
- case 'Note': {
428
- attrs.gff_note = val
429
- break
430
- }
431
- case 'Dbxref': {
432
- attrs.gff_dbxref = val
433
- break
434
- }
435
- case 'Ontology_term': {
436
- const goTerms: string[] = []
437
- const otherTerms: string[] = []
438
- for (const v of val) {
439
- if (v.startsWith('GO:')) {
440
- goTerms.push(v)
441
- } else {
442
- otherTerms.push(v)
443
- }
444
- }
445
- if (goTerms.length > 0) {
446
- attrs['Gene Ontology'] = goTerms
447
- }
448
- if (otherTerms.length > 0) {
449
- attrs.gff_ontology_term = otherTerms
450
- }
451
- break
452
- }
453
- case 'Is_circular': {
454
- attrs.gff_is_circular = val
455
- break
456
- }
457
- default: {
458
- attrs[key.toLowerCase()] = val
459
- }
460
- }
461
- }
462
- }
463
- }
464
- }
465
- feature.attributes = attrs
466
- }
467
- return feature
468
24
  }
package/src/Change.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
2
  /* eslint-disable @typescript-eslint/restrict-template-expressions */
3
3
  import {
4
- AnnotationFeatureI,
4
+ AnnotationFeature,
5
5
  AnnotationFeatureSnapshot,
6
6
  ApolloAssemblyI,
7
+ BackendDriverType,
7
8
  CheckResultI,
8
9
  CheckResultSnapshot,
9
10
  } from '@apollo-annotation/mst'
@@ -28,12 +29,15 @@ export interface ClientDataStore {
28
29
  ): AppRootModel['internetAccounts'][0]
29
30
  loadFeatures(regions: Region[]): void
30
31
  loadRefSeq(regions: Region[]): void
31
- getFeature(featureId: string): AnnotationFeatureI | undefined
32
+ getFeature(featureId: string): AnnotationFeature | undefined
32
33
  addFeature(assemblyId: string, feature: AnnotationFeatureSnapshot): void
33
34
  deleteFeature(featureId: string): void
34
35
  deleteAssembly(assemblyId: string): void
35
36
  addCheckResults(checkResults: CheckResultSnapshot[]): void
36
- addAssembly(assemblyId: string): ApolloAssemblyI
37
+ addAssembly(
38
+ assemblyId: string,
39
+ backendDriverType?: BackendDriverType,
40
+ ): ApolloAssemblyI
37
41
  }
38
42
 
39
43
  export type SerializedChange = SerializedOperation
package/src/Operation.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  FileDocument,
11
11
  RefSeqChunkDocument,
12
12
  RefSeqDocument,
13
+ JBrowseConfigDocument,
13
14
  UserDocument,
14
15
  } from '@apollo-annotation/schemas'
15
16
  import type { LoggerService } from '@nestjs/common'
@@ -36,6 +37,7 @@ export interface ServerDataStore {
36
37
  refSeqChunkModel: Model<RefSeqChunkDocument>
37
38
  fileModel: Model<FileDocument>
38
39
  userModel: Model<UserDocument>
40
+ jbrowseConfigModel: Model<JBrowseConfigDocument>
39
41
  session: ClientSession
40
42
  filesService: {
41
43
  getFileStream(file: FileDocument): ReadStream
package/tsconfig.json CHANGED
@@ -14,7 +14,5 @@
14
14
  "strictPropertyInitialization": false,
15
15
  },
16
16
  "include": ["./src"],
17
- "references": [
18
- { "path": "../apollo-schemas"},
19
- ]
17
+ "references": [{ "path": "../apollo-schemas" }, { "path": "../apollo-mst" }],
20
18
  }