@apollo-annotation/mst 0.1.11
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 +1 -0
- package/package.json +21 -0
- package/src/AnnotationFeature.ts +243 -0
- package/src/ApolloAssembly.ts +34 -0
- package/src/ApolloRefSeq.ts +116 -0
- package/src/CheckResult.ts +17 -0
- package/src/index.ts +4 -0
- package/tsconfig.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# apollo-mst
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@apollo-annotation/mst",
|
|
3
|
+
"version": "0.1.11",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@jbrowse/core": "^2.7.0",
|
|
10
|
+
"mobx": "^6.6.1",
|
|
11
|
+
"mobx-state-tree": "^5.1.7",
|
|
12
|
+
"rxjs": "^7.4.0",
|
|
13
|
+
"tslib": "^2.3.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.1.6"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
5
|
+
import {
|
|
6
|
+
IAnyModelType,
|
|
7
|
+
Instance,
|
|
8
|
+
SnapshotIn,
|
|
9
|
+
SnapshotOrInstance,
|
|
10
|
+
cast,
|
|
11
|
+
getParentOfType,
|
|
12
|
+
getSnapshot,
|
|
13
|
+
types,
|
|
14
|
+
} from 'mobx-state-tree'
|
|
15
|
+
|
|
16
|
+
import { ApolloAssembly } from '.'
|
|
17
|
+
|
|
18
|
+
export const LateAnnotationFeature = types.late(
|
|
19
|
+
(): IAnyModelType => AnnotationFeature,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const Phase = types.maybe(
|
|
23
|
+
types.union(types.literal(0), types.literal(1), types.literal(2)),
|
|
24
|
+
)
|
|
25
|
+
export const AnnotationFeature = types
|
|
26
|
+
.model('AnnotationFeature', {
|
|
27
|
+
_id: types.identifier,
|
|
28
|
+
gffId: types.maybe(types.string), // ID from attributes if exists, otherwise gffId = _id
|
|
29
|
+
/** Reference sequence name */
|
|
30
|
+
refSeq: types.string,
|
|
31
|
+
/** Feature type */
|
|
32
|
+
type: types.string,
|
|
33
|
+
/** Feature location start coordinate */
|
|
34
|
+
start: types.number,
|
|
35
|
+
/** Feature location end coordinate */
|
|
36
|
+
end: types.number,
|
|
37
|
+
/**
|
|
38
|
+
* If the feature exists in multiple places, e.g. a CDS in a canonical SO
|
|
39
|
+
* gene, this gives the coordinates of the individual starts and ends. The
|
|
40
|
+
* start of the first location and the end of the last location should match
|
|
41
|
+
* the feature's start and end.
|
|
42
|
+
*/
|
|
43
|
+
discontinuousLocations: types.maybe(
|
|
44
|
+
types.array(
|
|
45
|
+
types.model({ start: types.number, end: types.number, phase: Phase }),
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
/** The strand on which the feature is located */
|
|
49
|
+
strand: types.maybe(types.union(types.literal(1), types.literal(-1))),
|
|
50
|
+
/** The feature's score */
|
|
51
|
+
score: types.maybe(types.number),
|
|
52
|
+
/**
|
|
53
|
+
* The feature's phase, which is required for certain features, e.g. CDS in a
|
|
54
|
+
* canonical SO gene
|
|
55
|
+
*/
|
|
56
|
+
phase: Phase,
|
|
57
|
+
/** Child features of this feature */
|
|
58
|
+
children: types.maybe(types.map(types.maybe(LateAnnotationFeature))),
|
|
59
|
+
/**
|
|
60
|
+
* Additional attributes of the feature. This could include name, source,
|
|
61
|
+
* note, dbxref, etc.
|
|
62
|
+
*/
|
|
63
|
+
attributes: types.map(types.array(types.string)),
|
|
64
|
+
})
|
|
65
|
+
.views((self) => ({
|
|
66
|
+
get length() {
|
|
67
|
+
return self.end - self.start
|
|
68
|
+
},
|
|
69
|
+
get featureId() {
|
|
70
|
+
return self.attributes.get('id')
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Possibly different from `start` because "The GFF3 format does not enforce
|
|
74
|
+
* a rule in which features must be wholly contained within the location of
|
|
75
|
+
* their parents"
|
|
76
|
+
*/
|
|
77
|
+
get min() {
|
|
78
|
+
let min = self.start
|
|
79
|
+
for (const [, child] of self.children ?? []) {
|
|
80
|
+
min = Math.min(min, child.min)
|
|
81
|
+
}
|
|
82
|
+
return min
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* Possibly different from `end` because "The GFF3 format does not enforce a
|
|
86
|
+
* rule in which features must be wholly contained within the location of
|
|
87
|
+
* their parents"
|
|
88
|
+
*/
|
|
89
|
+
get max() {
|
|
90
|
+
let max = self.end
|
|
91
|
+
for (const [, child] of self.children ?? []) {
|
|
92
|
+
max = Math.max(max, child.max)
|
|
93
|
+
}
|
|
94
|
+
return max
|
|
95
|
+
},
|
|
96
|
+
hasDescendant(featureId: string) {
|
|
97
|
+
const { children } = self
|
|
98
|
+
if (!children) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
for (const [id, child] of children) {
|
|
102
|
+
if (id === featureId) {
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
if (child.hasDescendant(featureId)) {
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
},
|
|
111
|
+
}))
|
|
112
|
+
.actions((self) => ({
|
|
113
|
+
setAttributes(attributes: Map<string, string[]>) {
|
|
114
|
+
self.attributes.clear()
|
|
115
|
+
for (const [key, value] of attributes.entries()) {
|
|
116
|
+
self.attributes.set(key, value)
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
setAttribute(key: string, value: string[]) {
|
|
120
|
+
self.attributes.merge({ [key]: value })
|
|
121
|
+
},
|
|
122
|
+
setType(type: string) {
|
|
123
|
+
self.type = type
|
|
124
|
+
},
|
|
125
|
+
setRefSeq(refSeq: string) {
|
|
126
|
+
self.refSeq = refSeq
|
|
127
|
+
},
|
|
128
|
+
setStart(start: number) {
|
|
129
|
+
if (start > self.end) {
|
|
130
|
+
throw new Error(`Start "${start}" is greater than end "${self.end}"`)
|
|
131
|
+
}
|
|
132
|
+
if (self.start !== start) {
|
|
133
|
+
self.start = start
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
setCDSDiscontinuousLocationStart(start: number, index: number) {
|
|
137
|
+
const dl = self.discontinuousLocations
|
|
138
|
+
if (dl && dl.length > 0 && dl[index].start !== start) {
|
|
139
|
+
dl[index].start = start
|
|
140
|
+
if (index === 0) {
|
|
141
|
+
self.start = start
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
setEnd(end: number) {
|
|
146
|
+
if (end < self.start) {
|
|
147
|
+
throw new Error(`End "${end}" is less than start "${self.start}"`)
|
|
148
|
+
}
|
|
149
|
+
if (self.end !== end) {
|
|
150
|
+
self.end = end
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
setCDSDiscontinuousLocationEnd(end: number, index: number) {
|
|
154
|
+
const dl = self.discontinuousLocations
|
|
155
|
+
if (dl && dl.length > 0 && dl[index].end !== end) {
|
|
156
|
+
dl[index].end = end
|
|
157
|
+
if (index === dl.length - 1) {
|
|
158
|
+
self.end = end
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
setStrand(strand?: 1 | -1) {
|
|
163
|
+
self.strand = strand
|
|
164
|
+
},
|
|
165
|
+
addChild(childFeature: AnnotationFeatureSnapshot) {
|
|
166
|
+
if (self.children && self.children.size > 0) {
|
|
167
|
+
const existingChildren = getSnapshot(self.children) ?? {}
|
|
168
|
+
self.children.clear()
|
|
169
|
+
for (const [, child] of Object.entries({
|
|
170
|
+
...existingChildren,
|
|
171
|
+
[childFeature._id]: childFeature,
|
|
172
|
+
}).sort(([, a], [, b]) => a.start - b.start)) {
|
|
173
|
+
self.children.put(child)
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
self.children = cast({})
|
|
177
|
+
self.children?.put(childFeature)
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
deleteChild(childFeatureId: string) {
|
|
181
|
+
self.children?.delete(childFeatureId)
|
|
182
|
+
},
|
|
183
|
+
}))
|
|
184
|
+
.actions((self) => ({
|
|
185
|
+
update({
|
|
186
|
+
children,
|
|
187
|
+
end,
|
|
188
|
+
refSeq,
|
|
189
|
+
start,
|
|
190
|
+
strand,
|
|
191
|
+
}: {
|
|
192
|
+
refSeq: string
|
|
193
|
+
start: number
|
|
194
|
+
end: number
|
|
195
|
+
strand?: 1 | -1
|
|
196
|
+
children?: SnapshotOrInstance<typeof LateAnnotationFeature>
|
|
197
|
+
}) {
|
|
198
|
+
self.setRefSeq(refSeq)
|
|
199
|
+
self.setStart(start)
|
|
200
|
+
self.setEnd(end)
|
|
201
|
+
self.setStrand(strand)
|
|
202
|
+
if (children) {
|
|
203
|
+
self.children = cast(children)
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
}))
|
|
207
|
+
// This views block has to be last to avoid:
|
|
208
|
+
// "'parent' is referenced directly or indirectly in its own type annotation."
|
|
209
|
+
.views((self) => ({
|
|
210
|
+
get parent() {
|
|
211
|
+
let parent: AnnotationFeatureI | undefined
|
|
212
|
+
try {
|
|
213
|
+
parent = getParentOfType(self, AnnotationFeature)
|
|
214
|
+
} catch {
|
|
215
|
+
// pass
|
|
216
|
+
}
|
|
217
|
+
return parent
|
|
218
|
+
},
|
|
219
|
+
get topLevelFeature(): AnnotationFeatureI {
|
|
220
|
+
let feature = self
|
|
221
|
+
let parent
|
|
222
|
+
do {
|
|
223
|
+
try {
|
|
224
|
+
parent = getParentOfType(feature, AnnotationFeature)
|
|
225
|
+
feature = parent
|
|
226
|
+
} catch {
|
|
227
|
+
parent = undefined
|
|
228
|
+
}
|
|
229
|
+
} while (parent)
|
|
230
|
+
return feature as AnnotationFeatureI
|
|
231
|
+
},
|
|
232
|
+
get assemblyId(): string {
|
|
233
|
+
return getParentOfType(self, ApolloAssembly)._id
|
|
234
|
+
},
|
|
235
|
+
}))
|
|
236
|
+
|
|
237
|
+
export type AnnotationFeatureI = Instance<typeof AnnotationFeature>
|
|
238
|
+
type AnnotationFeatureSnapshotRaw = SnapshotIn<typeof AnnotationFeature>
|
|
239
|
+
export interface AnnotationFeatureSnapshot
|
|
240
|
+
extends AnnotationFeatureSnapshotRaw {
|
|
241
|
+
/** Child features of this feature */
|
|
242
|
+
children?: Record<string, AnnotationFeatureSnapshot>
|
|
243
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Instance, SnapshotIn, types } from 'mobx-state-tree'
|
|
2
|
+
|
|
3
|
+
import { ApolloRefSeq } from './ApolloRefSeq'
|
|
4
|
+
|
|
5
|
+
export const ApolloAssembly = types
|
|
6
|
+
.model('ApolloAssembly', {
|
|
7
|
+
_id: types.identifier,
|
|
8
|
+
refSeqs: types.map(ApolloRefSeq),
|
|
9
|
+
comments: types.array(types.string),
|
|
10
|
+
backendDriverType: types.optional(
|
|
11
|
+
types.enumeration('backendDriverType', [
|
|
12
|
+
'CollaborationServerDriver',
|
|
13
|
+
'InMemoryFileDriver',
|
|
14
|
+
'DesktopFileDriver',
|
|
15
|
+
]),
|
|
16
|
+
'CollaborationServerDriver',
|
|
17
|
+
),
|
|
18
|
+
})
|
|
19
|
+
.views((self) => ({
|
|
20
|
+
getByRefName(refName: string) {
|
|
21
|
+
return [...self.refSeqs.values()].find((val) => val.name === refName)
|
|
22
|
+
},
|
|
23
|
+
}))
|
|
24
|
+
.actions((self) => ({
|
|
25
|
+
addRefSeq(id: string, name: string, description?: string) {
|
|
26
|
+
return self.refSeqs.put({ _id: id, name, description })
|
|
27
|
+
},
|
|
28
|
+
addComment(comment: string) {
|
|
29
|
+
return self.comments.push(comment)
|
|
30
|
+
},
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
export type ApolloAssemblyI = Instance<typeof ApolloAssembly>
|
|
34
|
+
export type ApolloAssemblySnapshot = SnapshotIn<typeof ApolloAssembly>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isContainedWithin } from '@jbrowse/core/util'
|
|
2
|
+
import {
|
|
3
|
+
Instance,
|
|
4
|
+
SnapshotIn,
|
|
5
|
+
SnapshotOrInstance,
|
|
6
|
+
types,
|
|
7
|
+
} from 'mobx-state-tree'
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
AnnotationFeature,
|
|
11
|
+
AnnotationFeatureSnapshot,
|
|
12
|
+
} from './AnnotationFeature'
|
|
13
|
+
|
|
14
|
+
export const Sequence = types.model({
|
|
15
|
+
start: types.number,
|
|
16
|
+
stop: types.number,
|
|
17
|
+
sequence: types.string,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
interface SequenceSnapshot {
|
|
21
|
+
start: number
|
|
22
|
+
stop: number
|
|
23
|
+
sequence: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const ApolloRefSeq = types
|
|
27
|
+
.model('ApolloRefSeq', {
|
|
28
|
+
_id: types.identifier,
|
|
29
|
+
name: types.string,
|
|
30
|
+
description: '',
|
|
31
|
+
features: types.map(AnnotationFeature),
|
|
32
|
+
sequence: types.array(Sequence),
|
|
33
|
+
})
|
|
34
|
+
.actions((self) => ({
|
|
35
|
+
addFeature(feature: AnnotationFeatureSnapshot) {
|
|
36
|
+
self.features.put(feature)
|
|
37
|
+
},
|
|
38
|
+
deleteFeature(featureId: string) {
|
|
39
|
+
return self.features.delete(featureId)
|
|
40
|
+
},
|
|
41
|
+
setDescription(description: string) {
|
|
42
|
+
self.description = description
|
|
43
|
+
},
|
|
44
|
+
addSequence(seq: SnapshotOrInstance<typeof Sequence>) {
|
|
45
|
+
if (seq.sequence.length !== seq.stop - seq.start) {
|
|
46
|
+
throw new Error('sequence does not match declared length')
|
|
47
|
+
}
|
|
48
|
+
if (self.sequence.length === 0) {
|
|
49
|
+
self.sequence.push(seq)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
const newSequences: SequenceSnapshot[] = self.sequence.map((s) => ({
|
|
53
|
+
start: s.start,
|
|
54
|
+
stop: s.stop,
|
|
55
|
+
sequence: s.sequence,
|
|
56
|
+
}))
|
|
57
|
+
newSequences.push({
|
|
58
|
+
start: seq.start,
|
|
59
|
+
stop: seq.stop,
|
|
60
|
+
sequence: seq.sequence,
|
|
61
|
+
})
|
|
62
|
+
newSequences.sort((s1, s2) => s1.start - s2.start)
|
|
63
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
64
|
+
const consolidatedSequences = newSequences.reduce<SequenceSnapshot[]>(
|
|
65
|
+
(result, current) => {
|
|
66
|
+
const lastRange = result.at(-1)
|
|
67
|
+
if (lastRange === undefined) {
|
|
68
|
+
return [current]
|
|
69
|
+
}
|
|
70
|
+
if (lastRange.stop >= current.start) {
|
|
71
|
+
if (current.stop > lastRange.stop) {
|
|
72
|
+
lastRange.stop = current.stop
|
|
73
|
+
lastRange.sequence += current.sequence.slice(
|
|
74
|
+
current.stop - lastRange.stop,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
result.push(current)
|
|
79
|
+
}
|
|
80
|
+
return result
|
|
81
|
+
},
|
|
82
|
+
[],
|
|
83
|
+
)
|
|
84
|
+
if (
|
|
85
|
+
self.sequence.length === consolidatedSequences.length &&
|
|
86
|
+
self.sequence.every(
|
|
87
|
+
(s, idx) =>
|
|
88
|
+
s.start === consolidatedSequences[idx].start &&
|
|
89
|
+
s.stop === consolidatedSequences[idx].stop,
|
|
90
|
+
)
|
|
91
|
+
) {
|
|
92
|
+
// sequences was unchanged
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
self.sequence.clear()
|
|
96
|
+
self.sequence.push(...consolidatedSequences)
|
|
97
|
+
},
|
|
98
|
+
}))
|
|
99
|
+
.views((self) => ({
|
|
100
|
+
getSequence(start: number, stop: number): string {
|
|
101
|
+
for (const {
|
|
102
|
+
sequence,
|
|
103
|
+
start: seqStart,
|
|
104
|
+
stop: seqStop,
|
|
105
|
+
} of self.sequence) {
|
|
106
|
+
// adjacent to existing sequence - modify
|
|
107
|
+
if (isContainedWithin(start, stop, seqStart, seqStop)) {
|
|
108
|
+
return sequence.slice(start - seqStart, stop - seqStart)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return ''
|
|
112
|
+
},
|
|
113
|
+
}))
|
|
114
|
+
|
|
115
|
+
export type ApolloRefSeqI = Instance<typeof ApolloRefSeq>
|
|
116
|
+
export type ApolloRefSeqSnapshot = SnapshotIn<typeof ApolloRefSeq>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Instance, SnapshotIn, types } from 'mobx-state-tree'
|
|
2
|
+
|
|
3
|
+
import { AnnotationFeature } from './AnnotationFeature'
|
|
4
|
+
|
|
5
|
+
export const CheckResult = types.model('CheckResult', {
|
|
6
|
+
_id: types.identifier,
|
|
7
|
+
name: types.string,
|
|
8
|
+
ids: types.array(types.safeReference(AnnotationFeature)),
|
|
9
|
+
refSeq: types.string,
|
|
10
|
+
start: types.number,
|
|
11
|
+
end: types.number,
|
|
12
|
+
ignored: false,
|
|
13
|
+
message: types.string,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export type CheckResultI = Instance<typeof CheckResult>
|
|
17
|
+
export type CheckResultSnapshot = SnapshotIn<typeof CheckResult>
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"composite": true,
|
|
5
|
+
"incremental": true,
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"target": "ES2022",
|
|
9
|
+
"lib": ["ES2022", "DOM"],
|
|
10
|
+
"module": "CommonJS",
|
|
11
|
+
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo",
|
|
12
|
+
"experimentalDecorators": true,
|
|
13
|
+
"emitDecoratorMetadata": true,
|
|
14
|
+
"strictPropertyInitialization": false,
|
|
15
|
+
},
|
|
16
|
+
"include": ["./src"],
|
|
17
|
+
}
|