@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.
Files changed (127) hide show
  1. package/dist/Changes/AddAssemblyAliasesChange.d.ts +16 -0
  2. package/dist/Changes/AddAssemblyAliasesChange.js +50 -0
  3. package/dist/Changes/AddAssemblyAliasesChange.js.map +1 -0
  4. package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.d.ts +1 -1
  5. package/dist/Changes/AddAssemblyFromExternalChange.d.ts +1 -1
  6. package/dist/Changes/AddAssemblyFromFileChange.d.ts +1 -1
  7. package/dist/Changes/AddFeatureChange.d.ts +3 -2
  8. package/dist/Changes/AddFeatureChange.js +5 -25
  9. package/dist/Changes/AddFeatureChange.js.map +1 -1
  10. package/dist/Changes/AddFeaturesFromFileChange.d.ts +1 -1
  11. package/dist/Changes/AddRefSeqAliasesChange.d.ts +1 -1
  12. package/dist/Changes/DeleteAssemblyChange.d.ts +1 -1
  13. package/dist/Changes/DeleteFeatureChange.d.ts +12 -11
  14. package/dist/Changes/DeleteFeatureChange.js +37 -32
  15. package/dist/Changes/DeleteFeatureChange.js.map +1 -1
  16. package/dist/Changes/DeleteUserChange.d.ts +1 -1
  17. package/dist/Changes/FeatureAttributeChange.d.ts +3 -2
  18. package/dist/Changes/FeatureAttributeChange.js +14 -6
  19. package/dist/Changes/FeatureAttributeChange.js.map +1 -1
  20. package/dist/Changes/FromFileBaseChange.d.ts +3 -3
  21. package/dist/Changes/FromFileBaseChange.js.map +1 -1
  22. package/dist/Changes/ImportJBrowseConfigChange.d.ts +4 -1
  23. package/dist/Changes/ImportJBrowseConfigChange.js +17 -13
  24. package/dist/Changes/ImportJBrowseConfigChange.js.map +1 -1
  25. package/dist/Changes/LocationEndChange.d.ts +1 -1
  26. package/dist/Changes/LocationEndChange.js +40 -37
  27. package/dist/Changes/LocationEndChange.js.map +1 -1
  28. package/dist/Changes/LocationStartChange.d.ts +1 -1
  29. package/dist/Changes/LocationStartChange.js +40 -37
  30. package/dist/Changes/LocationStartChange.js.map +1 -1
  31. package/dist/Changes/MergeExonsChange.d.ts +30 -0
  32. package/dist/Changes/MergeExonsChange.js +130 -0
  33. package/dist/Changes/MergeExonsChange.js.map +1 -0
  34. package/dist/Changes/MergeTranscriptsChange.d.ts +36 -0
  35. package/dist/Changes/MergeTranscriptsChange.js +246 -0
  36. package/dist/Changes/MergeTranscriptsChange.js.map +1 -0
  37. package/dist/Changes/SplitExonChange.d.ts +33 -0
  38. package/dist/Changes/SplitExonChange.js +142 -0
  39. package/dist/Changes/SplitExonChange.js.map +1 -0
  40. package/dist/Changes/StrandChange.d.ts +1 -1
  41. package/dist/Changes/TypeChange.d.ts +1 -1
  42. package/dist/Changes/UndoMergeExonsChange.d.ts +27 -0
  43. package/dist/Changes/UndoMergeExonsChange.js +104 -0
  44. package/dist/Changes/UndoMergeExonsChange.js.map +1 -0
  45. package/dist/Changes/UndoMergeTranscriptsChange.d.ts +27 -0
  46. package/dist/Changes/UndoMergeTranscriptsChange.js +104 -0
  47. package/dist/Changes/UndoMergeTranscriptsChange.js.map +1 -0
  48. package/dist/Changes/UndoSplitExonChange.d.ts +32 -0
  49. package/dist/Changes/UndoSplitExonChange.js +111 -0
  50. package/dist/Changes/UndoSplitExonChange.js.map +1 -0
  51. package/dist/Changes/UserChange.d.ts +1 -1
  52. package/dist/Changes/index.d.ts +21 -0
  53. package/dist/Changes/index.js +21 -0
  54. package/dist/Changes/index.js.map +1 -1
  55. package/dist/Checks/CDSCheck.d.ts +1 -1
  56. package/dist/GFF3/annotationFeatureToGFF3.d.ts +2 -2
  57. package/dist/GFF3/annotationFeatureToGFF3.js +7 -1
  58. package/dist/GFF3/annotationFeatureToGFF3.js.map +1 -1
  59. package/dist/GFF3/annotationFeatureToGFF3.test.js +1 -1
  60. package/dist/GFF3/annotationFeatureToGFF3.test.js.map +1 -1
  61. package/dist/GFF3/gff3ToAnnotationFeature.d.ts +2 -2
  62. package/dist/GFF3/gff3ToAnnotationFeature.test.d.ts +1 -1
  63. package/dist/GFF3/gff3ToAnnotationFeature.test.js +1 -1
  64. package/dist/GFF3/gff3ToAnnotationFeature.test.js.map +1 -1
  65. package/dist/GFF3/gffReservedKeys.d.ts +7 -0
  66. package/dist/GFF3/gffReservedKeys.js +21 -1
  67. package/dist/GFF3/gffReservedKeys.js.map +1 -1
  68. package/dist/Messages.d.ts +2 -2
  69. package/dist/Operations/GetAssembliesOperation.d.ts +1 -1
  70. package/dist/Operations/GetFeaturesOperation.d.ts +1 -1
  71. package/dist/Validations/CoreValidation.d.ts +2 -2
  72. package/dist/Validations/CoreValidation.js +1 -1
  73. package/dist/Validations/CoreValidation.js.map +1 -1
  74. package/dist/Validations/ParentChildValidation.d.ts +5 -5
  75. package/dist/Validations/ParentChildValidation.js +3 -1
  76. package/dist/Validations/ParentChildValidation.js.map +1 -1
  77. package/dist/Validations/Validation.d.ts +5 -5
  78. package/dist/Validations/ValidationSet.d.ts +4 -4
  79. package/dist/Validations/ValidationSet.js.map +1 -1
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/dist/util.d.ts +2 -0
  82. package/dist/util.js +13 -0
  83. package/dist/util.js.map +1 -1
  84. package/package.json +4 -4
  85. package/src/Changes/AddAssemblyAliasesChange.ts +69 -0
  86. package/src/Changes/AddAssemblyAndFeaturesFromFileChange.ts +6 -6
  87. package/src/Changes/AddAssemblyFromExternalChange.ts +5 -5
  88. package/src/Changes/AddAssemblyFromFileChange.ts +5 -5
  89. package/src/Changes/AddFeatureChange.ts +11 -35
  90. package/src/Changes/AddFeaturesFromFileChange.ts +6 -6
  91. package/src/Changes/AddRefSeqAliasesChange.ts +6 -6
  92. package/src/Changes/DeleteAssemblyChange.ts +4 -4
  93. package/src/Changes/DeleteFeatureChange.ts +49 -43
  94. package/src/Changes/DeleteUserChange.ts +5 -5
  95. package/src/Changes/FeatureAttributeChange.ts +22 -13
  96. package/src/Changes/FromFileBaseChange.ts +6 -3
  97. package/src/Changes/ImportJBrowseConfigChange.ts +37 -22
  98. package/src/Changes/LocationEndChange.ts +47 -56
  99. package/src/Changes/LocationStartChange.ts +47 -56
  100. package/src/Changes/MergeExonsChange.ts +185 -0
  101. package/src/Changes/MergeTranscriptsChange.ts +379 -0
  102. package/src/Changes/SplitExonChange.ts +249 -0
  103. package/src/Changes/StrandChange.ts +6 -6
  104. package/src/Changes/TypeChange.ts +6 -6
  105. package/src/Changes/UndoMergeExonsChange.ts +149 -0
  106. package/src/Changes/UndoMergeTranscriptsChange.ts +153 -0
  107. package/src/Changes/UndoSplitExonChange.ts +174 -0
  108. package/src/Changes/UserChange.ts +5 -5
  109. package/src/Changes/index.ts +21 -0
  110. package/src/Checks/CDSCheck.ts +2 -2
  111. package/src/GFF3/annotationFeatureToGFF3.test.ts +4 -2
  112. package/src/GFF3/annotationFeatureToGFF3.ts +11 -5
  113. package/src/GFF3/gff3ToAnnotationFeature.test.ts +3 -3
  114. package/src/GFF3/gff3ToAnnotationFeature.ts +2 -2
  115. package/src/GFF3/gffReservedKeys.ts +25 -0
  116. package/src/Messages.ts +2 -2
  117. package/src/Operations/GetAssembliesOperation.ts +3 -3
  118. package/src/Operations/GetFeaturesOperation.ts +4 -4
  119. package/src/Validations/CoreValidation.ts +4 -3
  120. package/src/Validations/ParentChildValidation.ts +10 -8
  121. package/src/Validations/Validation.ts +5 -5
  122. package/src/Validations/ValidationSet.ts +8 -4
  123. package/src/util.ts +16 -0
  124. package/test_data/example01.json +3 -3
  125. package/test_data/example02.json +3 -3
  126. package/test_data/example04.json +3 -3
  127. 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'