@apollo-annotation/shared 0.1.18 → 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.
@@ -3,225 +3,225 @@ import {
3
3
  AnnotationFeatureSnapshot,
4
4
  CheckResultSnapshot,
5
5
  } from '@apollo-annotation/mst'
6
- import ObjectID from 'bson-objectid'
6
+ // import ObjectID from 'bson-objectid'
7
7
 
8
- enum STOP_CODONS {
9
- 'TAG',
10
- 'TAA',
11
- 'TGA',
12
- }
8
+ // enum STOP_CODONS {
9
+ // 'TAG',
10
+ // 'TAA',
11
+ // 'TGA',
12
+ // }
13
13
 
14
- const iupacComplements: Record<string, string | undefined> = {
15
- G: 'C',
16
- A: 'T',
17
- T: 'A',
18
- C: 'G',
19
- R /* G or A */: 'Y',
20
- Y /* T or C */: 'R',
21
- M /* A or C */: 'K',
22
- K /* G or T */: 'M',
23
- S /* G or C */: 'S',
24
- W /* A or T */: 'W',
25
- H /* A or C or T */: 'D',
26
- B /* G or T or C */: 'V',
27
- V /* G or C or A */: 'B',
28
- D /* G or A or T */: 'H',
29
- N /* G or A or T or C */: 'N',
30
- }
14
+ // const iupacComplements: Record<string, string | undefined> = {
15
+ // G: 'C',
16
+ // A: 'T',
17
+ // T: 'A',
18
+ // C: 'G',
19
+ // R /* G or A */: 'Y',
20
+ // Y /* T or C */: 'R',
21
+ // M /* A or C */: 'K',
22
+ // K /* G or T */: 'M',
23
+ // S /* G or C */: 'S',
24
+ // W /* A or T */: 'W',
25
+ // H /* A or C or T */: 'D',
26
+ // B /* G or T or C */: 'V',
27
+ // V /* G or C or A */: 'B',
28
+ // D /* G or A or T */: 'H',
29
+ // N /* G or A or T or C */: 'N',
30
+ // }
31
31
 
32
- function reverseComplement(dna: string): string {
33
- const complement: string[] = []
34
- for (const nt of dna) {
35
- const rc = iupacComplements[nt.toUpperCase()]
36
- if (rc === undefined) {
37
- throw new TypeError(`Cannot complement nucleotide: "${nt}"`)
38
- }
39
- if (nt === nt.toLowerCase()) {
40
- complement.push(rc.toLowerCase())
41
- } else {
42
- complement.push(rc)
43
- }
44
- }
45
- return complement.reverse().join('')
46
- }
32
+ // function reverseComplement(dna: string): string {
33
+ // const complement: string[] = []
34
+ // for (const nt of dna) {
35
+ // const rc = iupacComplements[nt.toUpperCase()]
36
+ // if (rc === undefined) {
37
+ // throw new TypeError(`Cannot complement nucleotide: "${nt}"`)
38
+ // }
39
+ // if (nt === nt.toLowerCase()) {
40
+ // complement.push(rc.toLowerCase())
41
+ // } else {
42
+ // complement.push(rc)
43
+ // }
44
+ // }
45
+ // return complement.reverse().join('')
46
+ // }
47
47
 
48
- async function getSequenceFromSingleFeature(
49
- feature: AnnotationFeatureSnapshot,
50
- getSequence: (start: number, end: number) => Promise<string>,
51
- ) {
52
- let seq = ''
53
- if (
54
- feature.discontinuousLocations !== undefined &&
55
- feature.discontinuousLocations.length > 0
56
- ) {
57
- for (const loc of feature.discontinuousLocations) {
58
- seq = seq + (await getSequence(loc.start, loc.end))
59
- }
60
- } else {
61
- seq = await getSequence(feature.start, feature.end)
62
- }
63
- if (feature.strand === -1) {
64
- return reverseComplement(seq)
65
- }
66
- return seq
67
- }
48
+ // async function getSequenceFromSingleFeature(
49
+ // feature: AnnotationFeatureSnapshot,
50
+ // getSequence: (start: number, end: number) => Promise<string>,
51
+ // ) {
52
+ // let seq = ''
53
+ // if (
54
+ // feature.discontinuousLocations !== undefined &&
55
+ // feature.discontinuousLocations.length > 0
56
+ // ) {
57
+ // for (const loc of feature.discontinuousLocations) {
58
+ // seq = seq + (await getSequence(loc.start, loc.end))
59
+ // }
60
+ // } else {
61
+ // seq = await getSequence(feature.start, feature.end)
62
+ // }
63
+ // if (feature.strand === -1) {
64
+ // return reverseComplement(seq)
65
+ // }
66
+ // return seq
67
+ // }
68
68
 
69
- async function getSequenceFromMultipleFeatures(
70
- features: AnnotationFeatureSnapshot[],
71
- getSequence: (start: number, end: number) => Promise<string>,
72
- ) {
73
- const strands = features.map((feature) => feature.strand)
74
- if (!strands.every((strand) => strand === strands[0])) {
75
- throw new Error(
76
- `Strands do not match in features: "${features
77
- .map((f) => f._id)
78
- .join(', ')}"`,
79
- )
80
- }
81
- let seq = ''
82
- for (const feature of features) {
83
- seq = seq + (await getSequence(feature.start, feature.end))
84
- }
85
- if (strands[0] === -1) {
86
- return reverseComplement(seq)
87
- }
88
- return seq
89
- }
69
+ // async function getSequenceFromMultipleFeatures(
70
+ // features: AnnotationFeatureSnapshot[],
71
+ // getSequence: (start: number, end: number) => Promise<string>,
72
+ // ) {
73
+ // const strands = features.map((feature) => feature.strand)
74
+ // if (!strands.every((strand) => strand === strands[0])) {
75
+ // throw new Error(
76
+ // `Strands do not match in features: "${features
77
+ // .map((f) => f._id)
78
+ // .join(', ')}"`,
79
+ // )
80
+ // }
81
+ // let seq = ''
82
+ // for (const feature of features) {
83
+ // seq = seq + (await getSequence(feature.start, feature.end))
84
+ // }
85
+ // if (strands[0] === -1) {
86
+ // return reverseComplement(seq)
87
+ // }
88
+ // return seq
89
+ // }
90
90
 
91
- function splitSequenceInCodons(cds: string): string[] {
92
- const codons: string[] = []
93
- for (let i = 0; i <= cds.length - 3; i += 3) {
94
- codons.push(cds.slice(i, i + 3))
95
- }
96
- return codons
97
- }
91
+ // function splitSequenceInCodons(cds: string): string[] {
92
+ // const codons: string[] = []
93
+ // for (let i = 0; i <= cds.length - 3; i += 3) {
94
+ // codons.push(cds.slice(i, i + 3))
95
+ // }
96
+ // return codons
97
+ // }
98
98
 
99
- function getOriginalCodonLocation(
100
- feature: AnnotationFeatureSnapshot | AnnotationFeatureSnapshot[],
101
- index: number,
102
- ): [number, number] {
103
- let lengthToStart = index * 3
104
- let lengthToEnd = lengthToStart + 3
105
- if (Array.isArray(feature)) {
106
- let startLocation: number | undefined = undefined,
107
- endLocation: number | undefined = undefined
108
- for (const f of feature) {
109
- const featureLength = f.end - f.start
110
- if (startLocation === undefined && featureLength > lengthToStart) {
111
- startLocation = f.start + lengthToStart
112
- } else {
113
- lengthToStart -= featureLength
114
- }
115
- if (endLocation === undefined && featureLength > lengthToEnd) {
116
- endLocation = f.start + lengthToEnd
117
- } else {
118
- lengthToEnd -= featureLength
119
- }
120
- if (startLocation !== undefined && endLocation !== undefined) {
121
- return [startLocation, endLocation]
122
- }
123
- }
124
- throw new Error('Could not determine original CDS location')
125
- } else {
126
- if (
127
- feature.discontinuousLocations !== undefined &&
128
- feature.discontinuousLocations.length > 0
129
- ) {
130
- let startLocation: number | undefined = undefined,
131
- endLocation: number | undefined = undefined
132
- for (const loc of feature.discontinuousLocations) {
133
- const locLength = loc.end - loc.start
134
- if (startLocation === undefined && locLength > lengthToStart) {
135
- startLocation = loc.start + lengthToStart
136
- } else {
137
- lengthToStart -= locLength
138
- }
139
- if (endLocation === undefined && locLength > lengthToEnd) {
140
- endLocation = loc.start + lengthToEnd
141
- } else {
142
- lengthToEnd -= locLength
143
- }
144
- if (startLocation !== undefined && endLocation !== undefined) {
145
- return [startLocation, endLocation]
146
- }
147
- }
148
- throw new Error('Could not determine original CDS location')
149
- } else {
150
- return [feature.start + lengthToStart, feature.start + lengthToEnd]
151
- }
152
- }
153
- }
99
+ // function getOriginalCodonLocation(
100
+ // feature: AnnotationFeatureSnapshot | AnnotationFeatureSnapshot[],
101
+ // index: number,
102
+ // ): [number, number] {
103
+ // let lengthToStart = index * 3
104
+ // let lengthToEnd = lengthToStart + 3
105
+ // if (Array.isArray(feature)) {
106
+ // let startLocation: number | undefined = undefined,
107
+ // endLocation: number | undefined = undefined
108
+ // for (const f of feature) {
109
+ // const featureLength = f.end - f.start
110
+ // if (startLocation === undefined && featureLength > lengthToStart) {
111
+ // startLocation = f.start + lengthToStart
112
+ // } else {
113
+ // lengthToStart -= featureLength
114
+ // }
115
+ // if (endLocation === undefined && featureLength > lengthToEnd) {
116
+ // endLocation = f.start + lengthToEnd
117
+ // } else {
118
+ // lengthToEnd -= featureLength
119
+ // }
120
+ // if (startLocation !== undefined && endLocation !== undefined) {
121
+ // return [startLocation, endLocation]
122
+ // }
123
+ // }
124
+ // throw new Error('Could not determine original CDS location')
125
+ // } else {
126
+ // if (
127
+ // feature.discontinuousLocations !== undefined &&
128
+ // feature.discontinuousLocations.length > 0
129
+ // ) {
130
+ // let startLocation: number | undefined = undefined,
131
+ // endLocation: number | undefined = undefined
132
+ // for (const loc of feature.discontinuousLocations) {
133
+ // const locLength = loc.end - loc.start
134
+ // if (startLocation === undefined && locLength > lengthToStart) {
135
+ // startLocation = loc.start + lengthToStart
136
+ // } else {
137
+ // lengthToStart -= locLength
138
+ // }
139
+ // if (endLocation === undefined && locLength > lengthToEnd) {
140
+ // endLocation = loc.start + lengthToEnd
141
+ // } else {
142
+ // lengthToEnd -= locLength
143
+ // }
144
+ // if (startLocation !== undefined && endLocation !== undefined) {
145
+ // return [startLocation, endLocation]
146
+ // }
147
+ // }
148
+ // throw new Error('Could not determine original CDS location')
149
+ // } else {
150
+ // return [feature.start + lengthToStart, feature.start + lengthToEnd]
151
+ // }
152
+ // }
153
+ // }
154
154
 
155
- async function checkCDS(
156
- feature: AnnotationFeatureSnapshot | AnnotationFeatureSnapshot[],
157
- getSequence: (start: number, end: number) => Promise<string>,
158
- ): Promise<CheckResultSnapshot[]> {
159
- const checkResults: CheckResultSnapshot[] = []
160
- let _id: string,
161
- ids: string[],
162
- start: number,
163
- end: number,
164
- refSeq: string,
165
- sequence: string
166
- if (Array.isArray(feature)) {
167
- sequence = await getSequenceFromMultipleFeatures(feature, getSequence)
168
- ids = feature.map((f) => f._id)
169
- _id = ids.join(',')
170
- ;[{ refSeq, start }] = feature
171
- const lastFeature = feature.at(-1)
172
- if (!lastFeature) {
173
- throw new Error('Zero-length feature array encountered')
174
- }
175
- ;({ end } = lastFeature)
176
- } else {
177
- sequence = await getSequenceFromSingleFeature(feature, getSequence)
178
- ;({ _id, end, refSeq, start } = feature)
179
- ids = [_id]
180
- }
181
- const codons = splitSequenceInCodons(sequence)
182
- if (sequence.length % 3 === 0) {
183
- const lastCodon = codons.pop() // Last codon is supposed to be a stop
184
- if (!lastCodon) {
185
- throw new Error(`No sequence found for feature "${_id}"`)
186
- }
187
- if (!(lastCodon.toUpperCase() in STOP_CODONS)) {
188
- checkResults.push({
189
- _id: new ObjectID().toHexString(),
190
- name: 'MissingStopCodonCheck',
191
- ids,
192
- refSeq: refSeq.toString(),
193
- start: end,
194
- end,
195
- message: `Feature "${_id}" is missing a stop codon`,
196
- })
197
- }
198
- } else {
199
- checkResults.push({
200
- _id: new ObjectID().toHexString(),
201
- name: 'MultipleOfThreeCheck',
202
- ids,
203
- refSeq: refSeq.toString(),
204
- start,
205
- end,
206
- message: `The coding sequence for feature "${_id}" is not a multiple of three`,
207
- })
208
- }
209
- for (const [idx, codon] of codons.entries()) {
210
- const [codonStart, codonEnd] = getOriginalCodonLocation(feature, idx)
211
- if (codon.toUpperCase() in STOP_CODONS) {
212
- checkResults.push({
213
- _id: new ObjectID().toHexString(),
214
- name: 'InternalStopCodonCheck',
215
- ids,
216
- refSeq: refSeq.toString(),
217
- start: codonStart,
218
- end: codonEnd,
219
- message: `The coding sequence for feature "${_id}" has an internal stop codon`,
220
- })
221
- }
222
- }
223
- return checkResults
224
- }
155
+ // async function checkCDS(
156
+ // feature: AnnotationFeatureSnapshot | AnnotationFeatureSnapshot[],
157
+ // getSequence: (start: number, end: number) => Promise<string>,
158
+ // ): Promise<CheckResultSnapshot[]> {
159
+ // const checkResults: CheckResultSnapshot[] = []
160
+ // let _id: string,
161
+ // ids: string[],
162
+ // start: number,
163
+ // end: number,
164
+ // refSeq: string,
165
+ // sequence: string
166
+ // if (Array.isArray(feature)) {
167
+ // sequence = await getSequenceFromMultipleFeatures(feature, getSequence)
168
+ // ids = feature.map((f) => f._id)
169
+ // _id = ids.join(',')
170
+ // ;[{ refSeq, start }] = feature
171
+ // const lastFeature = feature.at(-1)
172
+ // if (!lastFeature) {
173
+ // throw new Error('Zero-length feature array encountered')
174
+ // }
175
+ // ;({ end } = lastFeature)
176
+ // } else {
177
+ // sequence = await getSequenceFromSingleFeature(feature, getSequence)
178
+ // ;({ _id, end, refSeq, start } = feature)
179
+ // ids = [_id]
180
+ // }
181
+ // const codons = splitSequenceInCodons(sequence)
182
+ // if (sequence.length % 3 === 0) {
183
+ // const lastCodon = codons.pop() // Last codon is supposed to be a stop
184
+ // if (!lastCodon) {
185
+ // throw new Error(`No sequence found for feature "${_id}"`)
186
+ // }
187
+ // if (!(lastCodon.toUpperCase() in STOP_CODONS)) {
188
+ // checkResults.push({
189
+ // _id: new ObjectID().toHexString(),
190
+ // name: 'MissingStopCodonCheck',
191
+ // ids,
192
+ // refSeq: refSeq.toString(),
193
+ // start: end,
194
+ // end,
195
+ // message: `Feature "${_id}" is missing a stop codon`,
196
+ // })
197
+ // }
198
+ // } else {
199
+ // checkResults.push({
200
+ // _id: new ObjectID().toHexString(),
201
+ // name: 'MultipleOfThreeCheck',
202
+ // ids,
203
+ // refSeq: refSeq.toString(),
204
+ // start,
205
+ // end,
206
+ // message: `The coding sequence for feature "${_id}" is not a multiple of three`,
207
+ // })
208
+ // }
209
+ // for (const [idx, codon] of codons.entries()) {
210
+ // const [codonStart, codonEnd] = getOriginalCodonLocation(feature, idx)
211
+ // if (codon.toUpperCase() in STOP_CODONS) {
212
+ // checkResults.push({
213
+ // _id: new ObjectID().toHexString(),
214
+ // name: 'InternalStopCodonCheck',
215
+ // ids,
216
+ // refSeq: refSeq.toString(),
217
+ // start: codonStart,
218
+ // end: codonEnd,
219
+ // message: `The coding sequence for feature "${_id}" has an internal stop codon`,
220
+ // })
221
+ // }
222
+ // }
223
+ // return checkResults
224
+ // }
225
225
 
226
226
  export class CDSCheck extends Check {
227
227
  name = 'CDSCheck'
@@ -229,47 +229,49 @@ export class CDSCheck extends Check {
229
229
  default = true
230
230
 
231
231
  async checkFeature(
232
- feature: AnnotationFeatureSnapshot,
233
- getSequence: (start: number, end: number) => Promise<string>,
232
+ _feature: AnnotationFeatureSnapshot,
233
+ _getSequence: (start: number, end: number) => Promise<string>,
234
234
  ): Promise<CheckResultSnapshot[]> {
235
- if (feature.type === 'CDS') {
236
- return checkCDS(feature, getSequence)
237
- }
235
+ await Promise.resolve()
236
+ return []
237
+ // if (feature.type === 'CDS') {
238
+ // return checkCDS(feature, getSequence)
239
+ // }
238
240
 
239
- if (!feature.children) {
240
- return []
241
- }
241
+ // if (!feature.children) {
242
+ // return []
243
+ // }
242
244
 
243
- if (feature.type !== 'mRNA') {
244
- const checkResults: CheckResultSnapshot[] = []
245
- for (const child of Object.values(feature.children)) {
246
- checkResults.push(...(await this.checkFeature(child, getSequence)))
247
- }
248
- return checkResults
249
- }
245
+ // if (feature.type !== 'mRNA') {
246
+ // const checkResults: CheckResultSnapshot[] = []
247
+ // for (const child of Object.values(feature.children)) {
248
+ // checkResults.push(...(await this.checkFeature(child, getSequence)))
249
+ // }
250
+ // return checkResults
251
+ // }
250
252
 
251
- const cdsChildren = Object.values(feature.children).filter(
252
- (child) => child.type === 'CDS',
253
- )
254
- if (cdsChildren.length === 0) {
255
- throw new Error(`mRNA "${feature._id}" has no CDS children`)
256
- }
257
- const cdsChildrenWithDiscontinuousLocations = cdsChildren.filter(
258
- (child) =>
259
- child.discontinuousLocations && child.discontinuousLocations.length > 0,
260
- )
261
- if (cdsChildrenWithDiscontinuousLocations.length === 0) {
262
- return checkCDS(cdsChildren, getSequence)
263
- }
264
- if (cdsChildrenWithDiscontinuousLocations.length === cdsChildren.length) {
265
- const checkResults: CheckResultSnapshot[] = []
266
- for (const child of cdsChildren) {
267
- checkResults.push(...(await this.checkFeature(child, getSequence)))
268
- }
269
- return checkResults
270
- }
271
- throw new Error(
272
- `Mix of CDS with and without discontinuous locations found in mRNA "${feature._id}"`,
273
- )
253
+ // const cdsChildren = Object.values(feature.children).filter(
254
+ // (child) => child.type === 'CDS',
255
+ // )
256
+ // if (cdsChildren.length === 0) {
257
+ // throw new Error(`mRNA "${feature._id}" has no CDS children`)
258
+ // }
259
+ // const cdsChildrenWithDiscontinuousLocations = cdsChildren.filter(
260
+ // (child) =>
261
+ // child.discontinuousLocations && child.discontinuousLocations.length > 0,
262
+ // )
263
+ // if (cdsChildrenWithDiscontinuousLocations.length === 0) {
264
+ // return checkCDS(cdsChildren, getSequence)
265
+ // }
266
+ // if (cdsChildrenWithDiscontinuousLocations.length === cdsChildren.length) {
267
+ // const checkResults: CheckResultSnapshot[] = []
268
+ // for (const child of cdsChildren) {
269
+ // checkResults.push(...(await this.checkFeature(child, getSequence)))
270
+ // }
271
+ // return checkResults
272
+ // }
273
+ // throw new Error(
274
+ // `Mix of CDS with and without discontinuous locations found in mRNA "${feature._id}"`,
275
+ // )
274
276
  }
275
277
  }
@@ -3,7 +3,7 @@ import jwtDecode from 'jwt-decode'
3
3
  export interface JWTPayload {
4
4
  username: string
5
5
  email: string
6
- role?: 'admin' | 'user' | 'readOnly'
6
+ role?: 'admin' | 'user' | 'readOnly' | 'none'
7
7
  id: string
8
8
  }
9
9
 
@@ -0,0 +1,49 @@
1
+ /* eslint-disable @typescript-eslint/no-floating-promises */
2
+ import { strict as assert } from 'node:assert'
3
+ import { describe, it } from 'node:test'
4
+ import gff from '@gmod/gff'
5
+
6
+ import { gff3ToAnnotationFeature } from './gff3ToAnnotationFeature'
7
+ import { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
8
+
9
+ const testCases: [string, string, AnnotationFeatureSnapshot][] = [
10
+ [
11
+ 'a feature with no children',
12
+ 'ctgA example remark 1000 2000 . . . Name=Remark:hga;Alias=hga\n',
13
+ {
14
+ _id: '66c51f3e002c683eaf98a223',
15
+ refSeq: 'ctgA',
16
+ type: 'remark',
17
+ min: 999,
18
+ max: 2000,
19
+ attributes: {
20
+ gff_source: ['example'],
21
+ gff_name: ['Remark:hga'],
22
+ gff_alias: ['hga'],
23
+ },
24
+ },
25
+ ],
26
+ ]
27
+
28
+ function compareFeatures(
29
+ feature1: AnnotationFeatureSnapshot,
30
+ feature2: AnnotationFeatureSnapshot,
31
+ ) {
32
+ assert.deepEqual(
33
+ { ...feature1, _id: undefined },
34
+ { ...feature2, _id: undefined },
35
+ )
36
+ }
37
+
38
+ describe('gff3ToAnnotationFeature', () => {
39
+ for (const testCase of testCases) {
40
+ const [description, featureLine, convertedFeature] = testCase
41
+ it(`converts ${description}`, () => {
42
+ const gff3Feature = gff.parseStringSync(featureLine, {
43
+ parseSequences: false,
44
+ })
45
+ const feature = gff3ToAnnotationFeature(gff3Feature[0])
46
+ compareFeatures(convertedFeature, feature)
47
+ })
48
+ }
49
+ })