@apollo-annotation/shared 0.3.5 → 0.3.7
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/dist/Changes/AddAssemblyAliasesChange.d.ts +16 -0
- package/dist/Changes/AddAssemblyAliasesChange.js +50 -0
- package/dist/Changes/AddAssemblyAliasesChange.js.map +1 -0
- package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.d.ts +1 -1
- package/dist/Changes/AddAssemblyFromExternalChange.d.ts +1 -1
- package/dist/Changes/AddAssemblyFromFileChange.d.ts +1 -1
- package/dist/Changes/AddFeatureChange.d.ts +3 -2
- package/dist/Changes/AddFeatureChange.js +5 -25
- package/dist/Changes/AddFeatureChange.js.map +1 -1
- package/dist/Changes/AddFeaturesFromFileChange.d.ts +1 -1
- package/dist/Changes/AddRefSeqAliasesChange.d.ts +1 -1
- package/dist/Changes/DeleteAssemblyChange.d.ts +1 -1
- package/dist/Changes/DeleteFeatureChange.d.ts +12 -11
- package/dist/Changes/DeleteFeatureChange.js +37 -32
- package/dist/Changes/DeleteFeatureChange.js.map +1 -1
- package/dist/Changes/DeleteUserChange.d.ts +1 -1
- package/dist/Changes/FeatureAttributeChange.d.ts +3 -2
- package/dist/Changes/FeatureAttributeChange.js +14 -6
- package/dist/Changes/FeatureAttributeChange.js.map +1 -1
- package/dist/Changes/FromFileBaseChange.d.ts +3 -3
- package/dist/Changes/FromFileBaseChange.js.map +1 -1
- package/dist/Changes/ImportJBrowseConfigChange.d.ts +4 -1
- package/dist/Changes/ImportJBrowseConfigChange.js +17 -13
- package/dist/Changes/ImportJBrowseConfigChange.js.map +1 -1
- package/dist/Changes/LocationEndChange.d.ts +1 -1
- package/dist/Changes/LocationEndChange.js +40 -37
- package/dist/Changes/LocationEndChange.js.map +1 -1
- package/dist/Changes/LocationStartChange.d.ts +1 -1
- package/dist/Changes/LocationStartChange.js +40 -37
- package/dist/Changes/LocationStartChange.js.map +1 -1
- package/dist/Changes/MergeExonsChange.d.ts +30 -0
- package/dist/Changes/MergeExonsChange.js +130 -0
- package/dist/Changes/MergeExonsChange.js.map +1 -0
- package/dist/Changes/MergeTranscriptsChange.d.ts +36 -0
- package/dist/Changes/MergeTranscriptsChange.js +246 -0
- package/dist/Changes/MergeTranscriptsChange.js.map +1 -0
- package/dist/Changes/SplitExonChange.d.ts +33 -0
- package/dist/Changes/SplitExonChange.js +142 -0
- package/dist/Changes/SplitExonChange.js.map +1 -0
- package/dist/Changes/StrandChange.d.ts +1 -1
- package/dist/Changes/TypeChange.d.ts +1 -1
- package/dist/Changes/UndoMergeExonsChange.d.ts +27 -0
- package/dist/Changes/UndoMergeExonsChange.js +104 -0
- package/dist/Changes/UndoMergeExonsChange.js.map +1 -0
- package/dist/Changes/UndoMergeTranscriptsChange.d.ts +27 -0
- package/dist/Changes/UndoMergeTranscriptsChange.js +104 -0
- package/dist/Changes/UndoMergeTranscriptsChange.js.map +1 -0
- package/dist/Changes/UndoSplitExonChange.d.ts +32 -0
- package/dist/Changes/UndoSplitExonChange.js +111 -0
- package/dist/Changes/UndoSplitExonChange.js.map +1 -0
- package/dist/Changes/UserChange.d.ts +1 -1
- package/dist/Changes/index.d.ts +21 -0
- package/dist/Changes/index.js +21 -0
- package/dist/Changes/index.js.map +1 -1
- package/dist/Checks/CDSCheck.d.ts +1 -1
- package/dist/GFF3/annotationFeatureToGFF3.d.ts +2 -2
- package/dist/GFF3/annotationFeatureToGFF3.js +7 -1
- package/dist/GFF3/annotationFeatureToGFF3.js.map +1 -1
- package/dist/GFF3/annotationFeatureToGFF3.test.js +1 -1
- package/dist/GFF3/annotationFeatureToGFF3.test.js.map +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.d.ts +2 -2
- package/dist/GFF3/gff3ToAnnotationFeature.test.d.ts +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.test.js +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.test.js.map +1 -1
- package/dist/GFF3/gffReservedKeys.d.ts +7 -0
- package/dist/GFF3/gffReservedKeys.js +21 -1
- package/dist/GFF3/gffReservedKeys.js.map +1 -1
- package/dist/Messages.d.ts +2 -2
- package/dist/Operations/GetAssembliesOperation.d.ts +1 -1
- package/dist/Operations/GetFeaturesOperation.d.ts +1 -1
- package/dist/Validations/CoreValidation.d.ts +2 -2
- package/dist/Validations/CoreValidation.js +1 -1
- package/dist/Validations/CoreValidation.js.map +1 -1
- package/dist/Validations/ParentChildValidation.d.ts +5 -5
- package/dist/Validations/ParentChildValidation.js +3 -1
- package/dist/Validations/ParentChildValidation.js.map +1 -1
- package/dist/Validations/Validation.d.ts +5 -5
- package/dist/Validations/ValidationSet.d.ts +4 -4
- package/dist/Validations/ValidationSet.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/util.d.ts +2 -0
- package/dist/util.js +13 -0
- package/dist/util.js.map +1 -1
- package/package.json +4 -4
- package/src/Changes/AddAssemblyAliasesChange.ts +69 -0
- package/src/Changes/AddAssemblyAndFeaturesFromFileChange.ts +6 -6
- package/src/Changes/AddAssemblyFromExternalChange.ts +5 -5
- package/src/Changes/AddAssemblyFromFileChange.ts +5 -5
- package/src/Changes/AddFeatureChange.ts +11 -35
- package/src/Changes/AddFeaturesFromFileChange.ts +6 -6
- package/src/Changes/AddRefSeqAliasesChange.ts +6 -6
- package/src/Changes/DeleteAssemblyChange.ts +4 -4
- package/src/Changes/DeleteFeatureChange.ts +49 -43
- package/src/Changes/DeleteUserChange.ts +5 -5
- package/src/Changes/FeatureAttributeChange.ts +22 -13
- package/src/Changes/FromFileBaseChange.ts +6 -3
- package/src/Changes/ImportJBrowseConfigChange.ts +37 -22
- package/src/Changes/LocationEndChange.ts +47 -56
- package/src/Changes/LocationStartChange.ts +47 -56
- package/src/Changes/MergeExonsChange.ts +185 -0
- package/src/Changes/MergeTranscriptsChange.ts +379 -0
- package/src/Changes/SplitExonChange.ts +249 -0
- package/src/Changes/StrandChange.ts +6 -6
- package/src/Changes/TypeChange.ts +6 -6
- package/src/Changes/UndoMergeExonsChange.ts +149 -0
- package/src/Changes/UndoMergeTranscriptsChange.ts +153 -0
- package/src/Changes/UndoSplitExonChange.ts +174 -0
- package/src/Changes/UserChange.ts +5 -5
- package/src/Changes/index.ts +21 -0
- package/src/Checks/CDSCheck.ts +2 -2
- package/src/GFF3/annotationFeatureToGFF3.test.ts +4 -2
- package/src/GFF3/annotationFeatureToGFF3.ts +11 -5
- package/src/GFF3/gff3ToAnnotationFeature.test.ts +3 -3
- package/src/GFF3/gff3ToAnnotationFeature.ts +2 -2
- package/src/GFF3/gffReservedKeys.ts +25 -0
- package/src/Messages.ts +2 -2
- package/src/Operations/GetAssembliesOperation.ts +3 -3
- package/src/Operations/GetFeaturesOperation.ts +4 -4
- package/src/Validations/CoreValidation.ts +4 -3
- package/src/Validations/ParentChildValidation.ts +10 -8
- package/src/Validations/Validation.ts +5 -5
- package/src/Validations/ValidationSet.ts +8 -4
- package/src/util.ts +16 -0
- package/test_data/example01.json +3 -3
- package/test_data/example02.json +3 -3
- package/test_data/example04.json +3 -3
- package/test_data/gene_representations.gff3 +686 -1686
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/* eslint-disable unicorn/prefer-structured-clone */
|
|
2
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type ChangeOptions,
|
|
7
|
+
type ClientDataStore,
|
|
8
|
+
FeatureChange,
|
|
9
|
+
type LocalGFF3DataStore,
|
|
10
|
+
type SerializedFeatureChange,
|
|
11
|
+
type ServerDataStore,
|
|
12
|
+
} from '@apollo-annotation/common'
|
|
13
|
+
import {
|
|
14
|
+
type AnnotationFeature,
|
|
15
|
+
type AnnotationFeatureSnapshot,
|
|
16
|
+
} from '@apollo-annotation/mst'
|
|
17
|
+
import { type Feature } from '@apollo-annotation/schemas'
|
|
18
|
+
import { doesIntersect2 } from '@jbrowse/core/util'
|
|
19
|
+
|
|
20
|
+
import { findAndDeleteChildFeature } from './DeleteFeatureChange'
|
|
21
|
+
import { UndoMergeTranscriptsChange } from './UndoMergeTranscriptsChange'
|
|
22
|
+
|
|
23
|
+
interface SerializedMergeTranscriptsChangeBase extends SerializedFeatureChange {
|
|
24
|
+
typeName: 'MergeTranscriptsChange'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MergeTranscriptsChangeDetails {
|
|
28
|
+
firstTranscript: AnnotationFeatureSnapshot
|
|
29
|
+
secondTranscript: AnnotationFeatureSnapshot
|
|
30
|
+
parentFeatureId: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface SerializedMergeTranscriptsChangeSingle
|
|
34
|
+
extends SerializedMergeTranscriptsChangeBase,
|
|
35
|
+
MergeTranscriptsChangeDetails {}
|
|
36
|
+
|
|
37
|
+
interface SerializedMergeTranscriptsChangeMultiple
|
|
38
|
+
extends SerializedMergeTranscriptsChangeBase {
|
|
39
|
+
changes: MergeTranscriptsChangeDetails[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SerializedMergeTranscriptsChange =
|
|
43
|
+
| SerializedMergeTranscriptsChangeSingle
|
|
44
|
+
| SerializedMergeTranscriptsChangeMultiple
|
|
45
|
+
export class MergeTranscriptsChange extends FeatureChange {
|
|
46
|
+
typeName = 'MergeTranscriptsChange' as const
|
|
47
|
+
changes: MergeTranscriptsChangeDetails[]
|
|
48
|
+
|
|
49
|
+
constructor(json: SerializedMergeTranscriptsChange, options?: ChangeOptions) {
|
|
50
|
+
super(json, options)
|
|
51
|
+
this.changes = 'changes' in json ? json.changes : [json]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
toJSON(): SerializedMergeTranscriptsChange {
|
|
55
|
+
const { assembly, changedIds, changes, typeName } = this
|
|
56
|
+
if (changes.length === 1) {
|
|
57
|
+
const [{ firstTranscript, secondTranscript, parentFeatureId }] = changes
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
typeName,
|
|
61
|
+
changedIds,
|
|
62
|
+
assembly,
|
|
63
|
+
firstTranscript,
|
|
64
|
+
secondTranscript,
|
|
65
|
+
parentFeatureId,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { typeName, changedIds, assembly, changes }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async executeOnServer(backend: ServerDataStore) {
|
|
72
|
+
const { featureModel, session } = backend
|
|
73
|
+
const { changes, logger } = this
|
|
74
|
+
for (const change of changes) {
|
|
75
|
+
const { firstTranscript, secondTranscript } = change
|
|
76
|
+
const topLevelFeature = await featureModel
|
|
77
|
+
.findOne({ allIds: firstTranscript._id })
|
|
78
|
+
.session(session)
|
|
79
|
+
.exec()
|
|
80
|
+
if (!topLevelFeature) {
|
|
81
|
+
const errMsg = `*** ERROR: The following featureId was not found in database ='${firstTranscript._id}'`
|
|
82
|
+
logger.error(errMsg)
|
|
83
|
+
throw new Error(errMsg)
|
|
84
|
+
}
|
|
85
|
+
const mergedTranscript = this.getFeatureFromId(
|
|
86
|
+
topLevelFeature,
|
|
87
|
+
firstTranscript._id,
|
|
88
|
+
)
|
|
89
|
+
if (!mergedTranscript) {
|
|
90
|
+
const errMsg = 'ERROR when searching feature by featureId'
|
|
91
|
+
logger.error(errMsg)
|
|
92
|
+
throw new Error(errMsg)
|
|
93
|
+
}
|
|
94
|
+
this.mergeTranscriptsOnServer(mergedTranscript, secondTranscript)
|
|
95
|
+
const deletedIds = findAndDeleteChildFeature(
|
|
96
|
+
topLevelFeature,
|
|
97
|
+
secondTranscript._id,
|
|
98
|
+
this,
|
|
99
|
+
)
|
|
100
|
+
deletedIds.push(secondTranscript._id)
|
|
101
|
+
topLevelFeature.allIds = topLevelFeature.allIds.filter(
|
|
102
|
+
(id) => !deletedIds.includes(id),
|
|
103
|
+
)
|
|
104
|
+
await topLevelFeature.save()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
mergeTranscriptsOnServer(
|
|
109
|
+
firstTranscript: Feature,
|
|
110
|
+
secondTranscript: AnnotationFeatureSnapshot,
|
|
111
|
+
) {
|
|
112
|
+
firstTranscript.min = Math.min(firstTranscript.min, secondTranscript.min)
|
|
113
|
+
firstTranscript.max = Math.max(firstTranscript.max, secondTranscript.max)
|
|
114
|
+
this.mergeTranscriptAttributes(firstTranscript, secondTranscript)
|
|
115
|
+
if (secondTranscript.children) {
|
|
116
|
+
for (const [, secondFeatureChild] of Object.entries(
|
|
117
|
+
secondTranscript.children,
|
|
118
|
+
)) {
|
|
119
|
+
this.mergeFeatureIntoTranscriptOnServer(
|
|
120
|
+
secondFeatureChild,
|
|
121
|
+
firstTranscript,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
mergeFeatureIntoTranscriptOnServer(
|
|
128
|
+
secondFeatureChild: AnnotationFeatureSnapshot,
|
|
129
|
+
firstTranscript: Feature,
|
|
130
|
+
) {
|
|
131
|
+
if (!firstTranscript.children) {
|
|
132
|
+
firstTranscript.children = new Map<string, Feature>()
|
|
133
|
+
}
|
|
134
|
+
let merged = false
|
|
135
|
+
let mrgChild: Feature | undefined
|
|
136
|
+
for (const [fKey, firstFeatureChild] of firstTranscript.children) {
|
|
137
|
+
if (!merged || !mrgChild) {
|
|
138
|
+
mrgChild = firstFeatureChild
|
|
139
|
+
}
|
|
140
|
+
if (
|
|
141
|
+
mrgChild.type === secondFeatureChild.type &&
|
|
142
|
+
mrgChild.type === firstFeatureChild.type &&
|
|
143
|
+
doesIntersect2(
|
|
144
|
+
secondFeatureChild.min,
|
|
145
|
+
secondFeatureChild.max,
|
|
146
|
+
mrgChild.min,
|
|
147
|
+
mrgChild.max,
|
|
148
|
+
) &&
|
|
149
|
+
doesIntersect2(
|
|
150
|
+
firstFeatureChild.min,
|
|
151
|
+
firstFeatureChild.max,
|
|
152
|
+
mrgChild.min,
|
|
153
|
+
mrgChild.max,
|
|
154
|
+
)
|
|
155
|
+
) {
|
|
156
|
+
mrgChild.min = Math.min(
|
|
157
|
+
secondFeatureChild.min,
|
|
158
|
+
mrgChild.min,
|
|
159
|
+
firstFeatureChild.min,
|
|
160
|
+
)
|
|
161
|
+
mrgChild.max = Math.max(
|
|
162
|
+
secondFeatureChild.max,
|
|
163
|
+
mrgChild.max,
|
|
164
|
+
firstFeatureChild.max,
|
|
165
|
+
)
|
|
166
|
+
const mergedAttrs = this.mergeAttributes(mrgChild, secondFeatureChild)
|
|
167
|
+
mrgChild.attributes = mergedAttrs
|
|
168
|
+
firstTranscript.children.delete(fKey)
|
|
169
|
+
merged = true
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (merged && mrgChild && secondFeatureChild.children) {
|
|
174
|
+
// Add the children of the source feature
|
|
175
|
+
// (secondFeatureChild.children) to the merged feature (mrgChild)
|
|
176
|
+
Object.entries(secondFeatureChild.children).map(([, child]) => {
|
|
177
|
+
this.addChild(mrgChild, child)
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (merged && mrgChild) {
|
|
182
|
+
this.addChild(
|
|
183
|
+
firstTranscript,
|
|
184
|
+
mrgChild as unknown as AnnotationFeatureSnapshot,
|
|
185
|
+
)
|
|
186
|
+
} else {
|
|
187
|
+
// This secondFeatureChild has no overlap with any feature in the
|
|
188
|
+
// receiving transcript so we add it as it is to the receiving transcript
|
|
189
|
+
this.addChild(firstTranscript, secondFeatureChild)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* --------------------------------- */
|
|
194
|
+
|
|
195
|
+
async executeOnClient(dataStore: ClientDataStore) {
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
197
|
+
if (!dataStore) {
|
|
198
|
+
throw new Error('No data store')
|
|
199
|
+
}
|
|
200
|
+
for (const [idx, changedId] of this.changedIds.entries()) {
|
|
201
|
+
const { firstTranscript, secondTranscript } = this.changes[idx]
|
|
202
|
+
const mergedTranscript = dataStore.getFeature(firstTranscript._id)
|
|
203
|
+
if (!mergedTranscript) {
|
|
204
|
+
throw new Error(`Could not find feature with identifier "${changedId}"`)
|
|
205
|
+
}
|
|
206
|
+
this.mergeTranscriptsOnClient(mergedTranscript, secondTranscript)
|
|
207
|
+
mergedTranscript.parent?.deleteChild(secondTranscript._id)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
mergeTranscriptsOnClient(
|
|
212
|
+
firstTranscript: AnnotationFeature,
|
|
213
|
+
secondTranscript: AnnotationFeatureSnapshot,
|
|
214
|
+
) {
|
|
215
|
+
firstTranscript.setMin(Math.min(firstTranscript.min, secondTranscript.min))
|
|
216
|
+
firstTranscript.setMax(Math.max(firstTranscript.max, secondTranscript.max))
|
|
217
|
+
|
|
218
|
+
this.mergeTranscriptAttributes(firstTranscript, secondTranscript)
|
|
219
|
+
|
|
220
|
+
if (secondTranscript.children) {
|
|
221
|
+
for (const [, secondFeatureChild] of Object.entries(
|
|
222
|
+
secondTranscript.children,
|
|
223
|
+
)) {
|
|
224
|
+
this.mergeFeatureIntoTranscriptOnClient(
|
|
225
|
+
secondFeatureChild,
|
|
226
|
+
firstTranscript,
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
mergeFeatureIntoTranscriptOnClient(
|
|
233
|
+
secondFeatureChild: AnnotationFeatureSnapshot,
|
|
234
|
+
firstTranscript: AnnotationFeature,
|
|
235
|
+
) {
|
|
236
|
+
if (!firstTranscript.children) {
|
|
237
|
+
firstTranscript.children = new Map<string, AnnotationFeature>()
|
|
238
|
+
}
|
|
239
|
+
let merged = false
|
|
240
|
+
let mrgChild: AnnotationFeature | undefined
|
|
241
|
+
let toDelete
|
|
242
|
+
for (const [, firstFeatureChild] of firstTranscript.children) {
|
|
243
|
+
if (!merged || !mrgChild) {
|
|
244
|
+
toDelete = false
|
|
245
|
+
mrgChild = firstFeatureChild
|
|
246
|
+
} else {
|
|
247
|
+
toDelete = true
|
|
248
|
+
}
|
|
249
|
+
if (
|
|
250
|
+
mrgChild.type === secondFeatureChild.type &&
|
|
251
|
+
mrgChild.type === firstFeatureChild.type &&
|
|
252
|
+
doesIntersect2(
|
|
253
|
+
secondFeatureChild.min,
|
|
254
|
+
secondFeatureChild.max,
|
|
255
|
+
mrgChild.min,
|
|
256
|
+
mrgChild.max,
|
|
257
|
+
) &&
|
|
258
|
+
doesIntersect2(
|
|
259
|
+
firstFeatureChild.min,
|
|
260
|
+
firstFeatureChild.max,
|
|
261
|
+
mrgChild.min,
|
|
262
|
+
mrgChild.max,
|
|
263
|
+
)
|
|
264
|
+
) {
|
|
265
|
+
mrgChild.setMin(
|
|
266
|
+
Math.min(secondFeatureChild.min, mrgChild.min, firstFeatureChild.min),
|
|
267
|
+
)
|
|
268
|
+
mrgChild.setMax(
|
|
269
|
+
Math.max(secondFeatureChild.max, mrgChild.max, firstFeatureChild.max),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
const mergedAttrs = this.mergeAttributes(mrgChild, secondFeatureChild)
|
|
273
|
+
Object.entries(mergedAttrs).map(([key, value]) => {
|
|
274
|
+
if (mrgChild) {
|
|
275
|
+
mrgChild.setAttribute(key, value)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
if (toDelete) {
|
|
279
|
+
firstTranscript.deleteChild(firstFeatureChild._id)
|
|
280
|
+
}
|
|
281
|
+
merged = true
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (merged && mrgChild && secondFeatureChild.children) {
|
|
286
|
+
Object.entries(secondFeatureChild.children).map(([, child]) => {
|
|
287
|
+
mrgChild.addChild(child)
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!(merged && mrgChild)) {
|
|
292
|
+
// This secondFeatureChild has no overlap with any feature in the
|
|
293
|
+
// receiving transcript so we add it as it is to the receiving transcript
|
|
294
|
+
firstTranscript.addChild(secondFeatureChild)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
299
|
+
isAnnotationFeature(obj: any): obj is AnnotationFeature {
|
|
300
|
+
return (
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
302
|
+
typeof obj.setMin === 'function' &&
|
|
303
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
304
|
+
typeof obj.setMax === 'function' &&
|
|
305
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
306
|
+
typeof obj.addChild === 'function' &&
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
308
|
+
typeof obj.deleteChild === 'function'
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
|
|
313
|
+
throw new Error('executeOnLocalGFF3 not implemented')
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/* Merge attributes from source into destination */
|
|
317
|
+
mergeAttributes(
|
|
318
|
+
destination: Feature | AnnotationFeature,
|
|
319
|
+
source: AnnotationFeatureSnapshot,
|
|
320
|
+
): Record<string, string[]> {
|
|
321
|
+
const destAttrs: Record<string, string[]> = destination.attributes
|
|
322
|
+
? JSON.parse(JSON.stringify(destination.attributes))
|
|
323
|
+
: {}
|
|
324
|
+
if (source.attributes) {
|
|
325
|
+
const sourceAttrs: Record<string, string[]> = JSON.parse(
|
|
326
|
+
JSON.stringify(source.attributes),
|
|
327
|
+
)
|
|
328
|
+
Object.entries(sourceAttrs).map(([key, value]) => {
|
|
329
|
+
if (!(key in destAttrs)) {
|
|
330
|
+
destAttrs[key] = []
|
|
331
|
+
}
|
|
332
|
+
value.map((x) => {
|
|
333
|
+
if (!destAttrs[key].includes(x)) {
|
|
334
|
+
destAttrs[key].push(x)
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
return destAttrs
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
mergeTranscriptAttributes(
|
|
343
|
+
firstTranscript: Feature | AnnotationFeature,
|
|
344
|
+
secondTranscript: AnnotationFeatureSnapshot,
|
|
345
|
+
) {
|
|
346
|
+
const txAttrs = this.mergeAttributes(firstTranscript, secondTranscript)
|
|
347
|
+
if (this.isAnnotationFeature(firstTranscript)) {
|
|
348
|
+
Object.entries(txAttrs).map(([key, value]) => {
|
|
349
|
+
firstTranscript.setAttribute(key, value)
|
|
350
|
+
})
|
|
351
|
+
} else {
|
|
352
|
+
firstTranscript.attributes = txAttrs
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
getInverse() {
|
|
357
|
+
const { assembly, changedIds, changes, logger } = this
|
|
358
|
+
const inverseChangedIds = [...changedIds].reverse()
|
|
359
|
+
const inverseChanges = [...changes]
|
|
360
|
+
.reverse()
|
|
361
|
+
.map((mergeTranscriptChange) => ({
|
|
362
|
+
transcriptsToRestore: [
|
|
363
|
+
mergeTranscriptChange.firstTranscript,
|
|
364
|
+
mergeTranscriptChange.secondTranscript,
|
|
365
|
+
],
|
|
366
|
+
parentFeatureId: mergeTranscriptChange.parentFeatureId,
|
|
367
|
+
}))
|
|
368
|
+
logger.debug?.(`INVERSE CHANGE '${JSON.stringify(inverseChanges)}'`)
|
|
369
|
+
return new UndoMergeTranscriptsChange(
|
|
370
|
+
{
|
|
371
|
+
changedIds: inverseChangedIds,
|
|
372
|
+
typeName: 'UndoMergeTranscriptsChange',
|
|
373
|
+
changes: inverseChanges,
|
|
374
|
+
assembly,
|
|
375
|
+
},
|
|
376
|
+
{ logger },
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
type ChangeOptions,
|
|
6
|
+
type ClientDataStore,
|
|
7
|
+
FeatureChange,
|
|
8
|
+
type LocalGFF3DataStore,
|
|
9
|
+
type SerializedFeatureChange,
|
|
10
|
+
type ServerDataStore,
|
|
11
|
+
} from '@apollo-annotation/common'
|
|
12
|
+
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
13
|
+
|
|
14
|
+
import { findAndDeleteChildFeature } from './DeleteFeatureChange'
|
|
15
|
+
import { UndoSplitExonChange } from './UndoSplitExonChange'
|
|
16
|
+
|
|
17
|
+
interface SerializedSplitExonChangeBase extends SerializedFeatureChange {
|
|
18
|
+
typeName: 'SplitExonChange'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SplitExonChangeDetails {
|
|
22
|
+
exonToBeSplit: AnnotationFeatureSnapshot
|
|
23
|
+
parentFeatureId: string
|
|
24
|
+
upstreamCut: number
|
|
25
|
+
downstreamCut: number
|
|
26
|
+
leftExonId: string
|
|
27
|
+
rightExonId: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SerializedSplitExonChangeSingle
|
|
31
|
+
extends SerializedSplitExonChangeBase,
|
|
32
|
+
SplitExonChangeDetails {}
|
|
33
|
+
|
|
34
|
+
interface SerializedSplitExonChangeMultiple
|
|
35
|
+
extends SerializedSplitExonChangeBase {
|
|
36
|
+
changes: SplitExonChangeDetails[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type SerializedSplitExonChange =
|
|
40
|
+
| SerializedSplitExonChangeSingle
|
|
41
|
+
| SerializedSplitExonChangeMultiple
|
|
42
|
+
export class SplitExonChange extends FeatureChange {
|
|
43
|
+
typeName = 'SplitExonChange' as const
|
|
44
|
+
changes: SplitExonChangeDetails[]
|
|
45
|
+
|
|
46
|
+
constructor(json: SerializedSplitExonChange, options?: ChangeOptions) {
|
|
47
|
+
super(json, options)
|
|
48
|
+
this.changes = 'changes' in json ? json.changes : [json]
|
|
49
|
+
}
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/class-literal-property-style
|
|
51
|
+
get notification() {
|
|
52
|
+
return 'Exon successfully split'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toJSON(): SerializedSplitExonChange {
|
|
56
|
+
const { assembly, changedIds, changes, typeName } = this
|
|
57
|
+
if (changes.length === 1) {
|
|
58
|
+
const [
|
|
59
|
+
{
|
|
60
|
+
exonToBeSplit,
|
|
61
|
+
parentFeatureId,
|
|
62
|
+
upstreamCut,
|
|
63
|
+
downstreamCut,
|
|
64
|
+
leftExonId,
|
|
65
|
+
rightExonId,
|
|
66
|
+
},
|
|
67
|
+
] = changes
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
typeName,
|
|
71
|
+
changedIds,
|
|
72
|
+
assembly,
|
|
73
|
+
exonToBeSplit,
|
|
74
|
+
parentFeatureId,
|
|
75
|
+
upstreamCut,
|
|
76
|
+
downstreamCut,
|
|
77
|
+
leftExonId,
|
|
78
|
+
rightExonId,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { typeName, changedIds, assembly, changes }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async executeOnServer(backend: ServerDataStore) {
|
|
85
|
+
const { featureModel, session } = backend
|
|
86
|
+
const { changes, logger } = this
|
|
87
|
+
for (const change of changes) {
|
|
88
|
+
const {
|
|
89
|
+
exonToBeSplit,
|
|
90
|
+
parentFeatureId,
|
|
91
|
+
upstreamCut,
|
|
92
|
+
downstreamCut,
|
|
93
|
+
leftExonId,
|
|
94
|
+
rightExonId,
|
|
95
|
+
} = change
|
|
96
|
+
const topLevelFeature = await featureModel
|
|
97
|
+
.findOne({ allIds: exonToBeSplit._id })
|
|
98
|
+
.session(session)
|
|
99
|
+
.exec()
|
|
100
|
+
if (!topLevelFeature) {
|
|
101
|
+
const errMsg = `*** ERROR: The following featureId was not found in database ='${exonToBeSplit._id}'`
|
|
102
|
+
logger.error(errMsg)
|
|
103
|
+
throw new Error(errMsg)
|
|
104
|
+
}
|
|
105
|
+
const tx = this.getFeatureFromId(topLevelFeature, parentFeatureId)
|
|
106
|
+
if (!tx?.children) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
'ERROR: There should be at least one child (i.e. the exon to be split)',
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const [leftExon, rightExon] = this.makeSplitExons(
|
|
113
|
+
exonToBeSplit,
|
|
114
|
+
upstreamCut,
|
|
115
|
+
downstreamCut,
|
|
116
|
+
leftExonId,
|
|
117
|
+
rightExonId,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
tx.children.set(leftExon._id, {
|
|
121
|
+
allIds: [],
|
|
122
|
+
...leftExon,
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
124
|
+
// @ts-expect-error
|
|
125
|
+
_id: leftExon._id,
|
|
126
|
+
})
|
|
127
|
+
tx.children.set(rightExon._id, {
|
|
128
|
+
allIds: [],
|
|
129
|
+
...rightExon,
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
131
|
+
// @ts-expect-error
|
|
132
|
+
_id: rightExon._id,
|
|
133
|
+
})
|
|
134
|
+
// Child features should be sorted for click and drag of gene glyphs to work properly
|
|
135
|
+
tx.children = new Map(
|
|
136
|
+
[...tx.children.entries()].sort((a, b) => a[1].min - b[1].min),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
const deletedIds = findAndDeleteChildFeature(
|
|
140
|
+
topLevelFeature,
|
|
141
|
+
exonToBeSplit._id,
|
|
142
|
+
this,
|
|
143
|
+
)
|
|
144
|
+
deletedIds.push(exonToBeSplit._id)
|
|
145
|
+
topLevelFeature.allIds = topLevelFeature.allIds.filter(
|
|
146
|
+
(id) => !deletedIds.includes(id),
|
|
147
|
+
)
|
|
148
|
+
topLevelFeature.allIds.push(leftExon._id, rightExon._id)
|
|
149
|
+
await topLevelFeature.save()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
|
|
154
|
+
throw new Error('executeOnLocalGFF3 not implemented')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async executeOnClient(dataStore: ClientDataStore) {
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
159
|
+
if (!dataStore) {
|
|
160
|
+
throw new Error('No data store')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const [idx] of this.changedIds.entries()) {
|
|
164
|
+
const {
|
|
165
|
+
exonToBeSplit,
|
|
166
|
+
parentFeatureId,
|
|
167
|
+
upstreamCut,
|
|
168
|
+
downstreamCut,
|
|
169
|
+
leftExonId,
|
|
170
|
+
rightExonId,
|
|
171
|
+
} = this.changes[idx]
|
|
172
|
+
if (!parentFeatureId) {
|
|
173
|
+
throw new Error('TODO: Split exon without parent')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const [leftExon, rightExon] = this.makeSplitExons(
|
|
177
|
+
exonToBeSplit,
|
|
178
|
+
upstreamCut,
|
|
179
|
+
downstreamCut,
|
|
180
|
+
leftExonId,
|
|
181
|
+
rightExonId,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
const parentFeature = dataStore.getFeature(parentFeatureId)
|
|
185
|
+
if (!parentFeature) {
|
|
186
|
+
throw new Error(`Could not find parent feature "${parentFeatureId}"`)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
parentFeature.addChild(leftExon)
|
|
190
|
+
parentFeature.addChild(rightExon)
|
|
191
|
+
if (dataStore.getFeature(exonToBeSplit._id)) {
|
|
192
|
+
dataStore.deleteFeature(exonToBeSplit._id)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getInverse() {
|
|
198
|
+
const { assembly, changedIds, changes, logger } = this
|
|
199
|
+
const inverseChangedIds = [...changedIds].reverse()
|
|
200
|
+
const inverseChanges = [...changes].reverse().map((splitExonChange) => ({
|
|
201
|
+
exonToRestore: splitExonChange.exonToBeSplit,
|
|
202
|
+
parentFeatureId: splitExonChange.parentFeatureId,
|
|
203
|
+
idsToDelete: [splitExonChange.leftExonId, splitExonChange.rightExonId],
|
|
204
|
+
upstreamCut: splitExonChange.upstreamCut,
|
|
205
|
+
downstreamCut: splitExonChange.downstreamCut,
|
|
206
|
+
leftExonId: splitExonChange.leftExonId,
|
|
207
|
+
rightExonId: splitExonChange.rightExonId,
|
|
208
|
+
}))
|
|
209
|
+
logger.debug?.(`INVERSE CHANGE '${JSON.stringify(inverseChanges)}'`)
|
|
210
|
+
return new UndoSplitExonChange(
|
|
211
|
+
{
|
|
212
|
+
changedIds: inverseChangedIds,
|
|
213
|
+
typeName: 'UndoSplitExonChange',
|
|
214
|
+
changes: inverseChanges,
|
|
215
|
+
assembly,
|
|
216
|
+
},
|
|
217
|
+
{ logger },
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
makeSplitExons(
|
|
222
|
+
exonToBeSplit: AnnotationFeatureSnapshot,
|
|
223
|
+
upstreamCut: number,
|
|
224
|
+
downstreamCut: number,
|
|
225
|
+
leftExonId: string,
|
|
226
|
+
rightExonId: string,
|
|
227
|
+
): AnnotationFeatureSnapshot[] {
|
|
228
|
+
// eslint-disable-next-line unicorn/prefer-structured-clone
|
|
229
|
+
const exon = JSON.parse(JSON.stringify(exonToBeSplit))
|
|
230
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
231
|
+
delete exon.attributes._id
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
233
|
+
delete exon.attributes.gff_id
|
|
234
|
+
|
|
235
|
+
const leftExon = structuredClone(
|
|
236
|
+
exon,
|
|
237
|
+
) as unknown as AnnotationFeatureSnapshot
|
|
238
|
+
leftExon._id = leftExonId
|
|
239
|
+
leftExon.max = upstreamCut
|
|
240
|
+
|
|
241
|
+
const rightExon = structuredClone(
|
|
242
|
+
exon,
|
|
243
|
+
) as unknown as AnnotationFeatureSnapshot
|
|
244
|
+
rightExon.min = downstreamCut
|
|
245
|
+
rightExon._id = rightExonId
|
|
246
|
+
|
|
247
|
+
return [leftExon, rightExon]
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/require-await */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
4
4
|
import {
|
|
5
|
-
ChangeOptions,
|
|
6
|
-
ClientDataStore,
|
|
5
|
+
type ChangeOptions,
|
|
6
|
+
type ClientDataStore,
|
|
7
7
|
FeatureChange,
|
|
8
|
-
LocalGFF3DataStore,
|
|
9
|
-
SerializedFeatureChange,
|
|
10
|
-
ServerDataStore,
|
|
8
|
+
type LocalGFF3DataStore,
|
|
9
|
+
type SerializedFeatureChange,
|
|
10
|
+
type ServerDataStore,
|
|
11
11
|
} from '@apollo-annotation/common'
|
|
12
|
-
import { Feature, FeatureDocument } from '@apollo-annotation/schemas'
|
|
12
|
+
import { type Feature, type FeatureDocument } from '@apollo-annotation/schemas'
|
|
13
13
|
|
|
14
14
|
interface SerializedStrandChangeBase extends SerializedFeatureChange {
|
|
15
15
|
typeName: 'StrandChange'
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/require-await */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
4
4
|
import {
|
|
5
|
-
ChangeOptions,
|
|
6
|
-
ClientDataStore,
|
|
5
|
+
type ChangeOptions,
|
|
6
|
+
type ClientDataStore,
|
|
7
7
|
FeatureChange,
|
|
8
|
-
LocalGFF3DataStore,
|
|
9
|
-
SerializedFeatureChange,
|
|
10
|
-
ServerDataStore,
|
|
8
|
+
type LocalGFF3DataStore,
|
|
9
|
+
type SerializedFeatureChange,
|
|
10
|
+
type ServerDataStore,
|
|
11
11
|
} from '@apollo-annotation/common'
|
|
12
|
-
import { Feature, FeatureDocument } from '@apollo-annotation/schemas'
|
|
12
|
+
import { type Feature, type FeatureDocument } from '@apollo-annotation/schemas'
|
|
13
13
|
|
|
14
14
|
interface SerializedTypeChangeBase extends SerializedFeatureChange {
|
|
15
15
|
typeName: 'TypeChange'
|