@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 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
@@ -0,0 +1,4 @@
1
+ export * from './AnnotationFeature'
2
+ export * from './ApolloAssembly'
3
+ export * from './ApolloRefSeq'
4
+ export * from './CheckResult'
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
+ }