@apollo-annotation/shared 0.3.6 → 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 (66) 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/AddFeatureChange.d.ts +1 -0
  5. package/dist/Changes/AddFeatureChange.js +5 -25
  6. package/dist/Changes/AddFeatureChange.js.map +1 -1
  7. package/dist/Changes/DeleteFeatureChange.d.ts +9 -8
  8. package/dist/Changes/DeleteFeatureChange.js +37 -32
  9. package/dist/Changes/DeleteFeatureChange.js.map +1 -1
  10. package/dist/Changes/FeatureAttributeChange.d.ts +2 -1
  11. package/dist/Changes/FeatureAttributeChange.js +14 -6
  12. package/dist/Changes/FeatureAttributeChange.js.map +1 -1
  13. package/dist/Changes/ImportJBrowseConfigChange.d.ts +3 -0
  14. package/dist/Changes/ImportJBrowseConfigChange.js +17 -13
  15. package/dist/Changes/ImportJBrowseConfigChange.js.map +1 -1
  16. package/dist/Changes/LocationEndChange.js +40 -37
  17. package/dist/Changes/LocationEndChange.js.map +1 -1
  18. package/dist/Changes/LocationStartChange.js +40 -37
  19. package/dist/Changes/LocationStartChange.js.map +1 -1
  20. package/dist/Changes/MergeExonsChange.d.ts +30 -0
  21. package/dist/Changes/MergeExonsChange.js +130 -0
  22. package/dist/Changes/MergeExonsChange.js.map +1 -0
  23. package/dist/Changes/MergeTranscriptsChange.d.ts +36 -0
  24. package/dist/Changes/MergeTranscriptsChange.js +246 -0
  25. package/dist/Changes/MergeTranscriptsChange.js.map +1 -0
  26. package/dist/Changes/SplitExonChange.d.ts +33 -0
  27. package/dist/Changes/SplitExonChange.js +142 -0
  28. package/dist/Changes/SplitExonChange.js.map +1 -0
  29. package/dist/Changes/UndoMergeExonsChange.d.ts +27 -0
  30. package/dist/Changes/UndoMergeExonsChange.js +104 -0
  31. package/dist/Changes/UndoMergeExonsChange.js.map +1 -0
  32. package/dist/Changes/UndoMergeTranscriptsChange.d.ts +27 -0
  33. package/dist/Changes/UndoMergeTranscriptsChange.js +104 -0
  34. package/dist/Changes/UndoMergeTranscriptsChange.js.map +1 -0
  35. package/dist/Changes/UndoSplitExonChange.d.ts +32 -0
  36. package/dist/Changes/UndoSplitExonChange.js +111 -0
  37. package/dist/Changes/UndoSplitExonChange.js.map +1 -0
  38. package/dist/Changes/index.d.ts +21 -0
  39. package/dist/Changes/index.js +21 -0
  40. package/dist/Changes/index.js.map +1 -1
  41. package/dist/GFF3/annotationFeatureToGFF3.js +7 -1
  42. package/dist/GFF3/annotationFeatureToGFF3.js.map +1 -1
  43. package/dist/Validations/ParentChildValidation.js +3 -1
  44. package/dist/Validations/ParentChildValidation.js.map +1 -1
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/dist/util.d.ts +2 -0
  47. package/dist/util.js +13 -0
  48. package/dist/util.js.map +1 -1
  49. package/package.json +4 -4
  50. package/src/Changes/AddAssemblyAliasesChange.ts +69 -0
  51. package/src/Changes/AddFeatureChange.ts +5 -29
  52. package/src/Changes/DeleteFeatureChange.ts +42 -36
  53. package/src/Changes/FeatureAttributeChange.ts +16 -7
  54. package/src/Changes/ImportJBrowseConfigChange.ts +33 -18
  55. package/src/Changes/LocationEndChange.ts +41 -50
  56. package/src/Changes/LocationStartChange.ts +41 -50
  57. package/src/Changes/MergeExonsChange.ts +185 -0
  58. package/src/Changes/MergeTranscriptsChange.ts +379 -0
  59. package/src/Changes/SplitExonChange.ts +249 -0
  60. package/src/Changes/UndoMergeExonsChange.ts +149 -0
  61. package/src/Changes/UndoMergeTranscriptsChange.ts +153 -0
  62. package/src/Changes/UndoSplitExonChange.ts +174 -0
  63. package/src/Changes/index.ts +21 -0
  64. package/src/GFF3/annotationFeatureToGFF3.ts +7 -1
  65. package/src/Validations/ParentChildValidation.ts +3 -2
  66. package/src/util.ts +16 -0
@@ -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
+ }
@@ -0,0 +1,149 @@
1
+ /* eslint-disable @typescript-eslint/require-await */
2
+
3
+ import {
4
+ type ChangeOptions,
5
+ type ClientDataStore,
6
+ FeatureChange,
7
+ type LocalGFF3DataStore,
8
+ type SerializedFeatureChange,
9
+ type ServerDataStore,
10
+ } from '@apollo-annotation/common'
11
+ import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
12
+
13
+ import { MergeExonsChange } from './MergeExonsChange'
14
+
15
+ interface SerializedUndoMergeExonsChangeBase extends SerializedFeatureChange {
16
+ typeName: 'UndoMergeExonsChange'
17
+ }
18
+
19
+ export interface UndoMergeExonsChangeDetails {
20
+ exonsToRestore: AnnotationFeatureSnapshot[]
21
+ parentFeatureId: string
22
+ }
23
+
24
+ interface SerializedUndoMergeExonsChangeSingle
25
+ extends SerializedUndoMergeExonsChangeBase,
26
+ UndoMergeExonsChangeDetails {}
27
+
28
+ interface SerializedUndoMergeExonsChangeMultiple
29
+ extends SerializedUndoMergeExonsChangeBase {
30
+ changes: UndoMergeExonsChangeDetails[]
31
+ }
32
+
33
+ export type SerializedUndoMergeExonsChange =
34
+ | SerializedUndoMergeExonsChangeSingle
35
+ | SerializedUndoMergeExonsChangeMultiple
36
+ export class UndoMergeExonsChange extends FeatureChange {
37
+ typeName = 'UndoMergeExonsChange' as const
38
+ changes: UndoMergeExonsChangeDetails[]
39
+
40
+ constructor(json: SerializedUndoMergeExonsChange, options?: ChangeOptions) {
41
+ super(json, options)
42
+ this.changes = 'changes' in json ? json.changes : [json]
43
+ }
44
+
45
+ toJSON(): SerializedUndoMergeExonsChange {
46
+ const { assembly, changedIds, changes, typeName } = this
47
+ if (changes.length === 1) {
48
+ const [{ exonsToRestore, parentFeatureId }] = changes
49
+
50
+ return {
51
+ typeName,
52
+ changedIds,
53
+ assembly,
54
+ exonsToRestore,
55
+ parentFeatureId,
56
+ }
57
+ }
58
+ return { typeName, changedIds, assembly, changes }
59
+ }
60
+
61
+ async executeOnServer(backend: ServerDataStore) {
62
+ const { featureModel, session } = backend
63
+ const { changes } = this
64
+ for (const change of changes) {
65
+ const { exonsToRestore, parentFeatureId } = change
66
+ if (exonsToRestore.length !== 2) {
67
+ throw new Error(
68
+ `Expected exactly two exons to restore. Got :${exonsToRestore.length}`,
69
+ )
70
+ }
71
+ const topLevelFeature = await featureModel
72
+ .findOne({ allIds: parentFeatureId })
73
+ .session(session)
74
+ .exec()
75
+ if (!topLevelFeature) {
76
+ throw new Error(`Could not find feature with ID "${parentFeatureId}"`)
77
+ }
78
+ const parentFeature = this.getFeatureFromId(
79
+ topLevelFeature,
80
+ parentFeatureId,
81
+ )
82
+ if (!parentFeature) {
83
+ throw new Error(
84
+ `Could not find feature with ID "${parentFeatureId}" in feature "${topLevelFeature._id.toString()}"`,
85
+ )
86
+ }
87
+ if (!parentFeature.children) {
88
+ parentFeature.children = new Map()
89
+ }
90
+ for (const exon of exonsToRestore) {
91
+ this.addChild(parentFeature, exon)
92
+ const childIds = this.getChildFeatureIds(exon)
93
+ topLevelFeature.allIds.push(exon._id, ...childIds)
94
+ }
95
+ await topLevelFeature.save()
96
+ }
97
+ }
98
+
99
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
100
+ throw new Error('executeOnLocalGFF3 not implemented')
101
+ }
102
+
103
+ async executeOnClient(dataStore: ClientDataStore) {
104
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
105
+ if (!dataStore) {
106
+ throw new Error('No data store')
107
+ }
108
+ const { changes } = this
109
+ for (const change of changes) {
110
+ const { exonsToRestore, parentFeatureId } = change
111
+ if (!parentFeatureId) {
112
+ throw new Error('Parent ID is missing')
113
+ }
114
+ const parentFeature = dataStore.getFeature(parentFeatureId)
115
+ if (!parentFeature) {
116
+ throw new Error(`Could not find parent feature "${parentFeatureId}"`)
117
+ }
118
+ // create an ID for the parent feature if it does not have one
119
+ if (!parentFeature.attributes.get('_id')) {
120
+ parentFeature.setAttribute('_id', [parentFeature._id])
121
+ }
122
+ for (const exon of exonsToRestore) {
123
+ parentFeature.addChild(exon)
124
+ }
125
+ }
126
+ }
127
+
128
+ getInverse() {
129
+ const { assembly, changedIds, changes, logger } = this
130
+ const inverseChangedIds = [...changedIds].reverse()
131
+ const inverseChanges = [...changes]
132
+ .reverse()
133
+ .map((undoMergeExonsChange) => ({
134
+ firstExon: undoMergeExonsChange.exonsToRestore[0],
135
+ secondExon: undoMergeExonsChange.exonsToRestore[1],
136
+ parentFeatureId: undoMergeExonsChange.parentFeatureId,
137
+ }))
138
+
139
+ return new MergeExonsChange(
140
+ {
141
+ changedIds: inverseChangedIds,
142
+ typeName: 'MergeExonsChange',
143
+ changes: inverseChanges,
144
+ assembly,
145
+ },
146
+ { logger },
147
+ )
148
+ }
149
+ }
@@ -0,0 +1,153 @@
1
+ /* eslint-disable @typescript-eslint/require-await */
2
+
3
+ import {
4
+ type ChangeOptions,
5
+ type ClientDataStore,
6
+ FeatureChange,
7
+ type LocalGFF3DataStore,
8
+ type SerializedFeatureChange,
9
+ type ServerDataStore,
10
+ } from '@apollo-annotation/common'
11
+ import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
12
+
13
+ import { MergeTranscriptsChange } from './MergeTranscriptsChange'
14
+
15
+ interface SerializedUndoMergeTranscriptsChangeBase
16
+ extends SerializedFeatureChange {
17
+ typeName: 'UndoMergeTranscriptsChange'
18
+ }
19
+
20
+ export interface UndoMergeTranscriptsChangeDetails {
21
+ transcriptsToRestore: AnnotationFeatureSnapshot[]
22
+ parentFeatureId: string
23
+ }
24
+
25
+ interface SerializedUndoMergeTranscriptsChangeSingle
26
+ extends SerializedUndoMergeTranscriptsChangeBase,
27
+ UndoMergeTranscriptsChangeDetails {}
28
+
29
+ interface SerializedUndoMergeTranscriptsChangeMultiple
30
+ extends SerializedUndoMergeTranscriptsChangeBase {
31
+ changes: UndoMergeTranscriptsChangeDetails[]
32
+ }
33
+
34
+ export type SerializedUndoMergeTranscriptsChange =
35
+ | SerializedUndoMergeTranscriptsChangeSingle
36
+ | SerializedUndoMergeTranscriptsChangeMultiple
37
+ export class UndoMergeTranscriptsChange extends FeatureChange {
38
+ typeName = 'UndoMergeTranscriptsChange' as const
39
+ changes: UndoMergeTranscriptsChangeDetails[]
40
+
41
+ constructor(
42
+ json: SerializedUndoMergeTranscriptsChange,
43
+ options?: ChangeOptions,
44
+ ) {
45
+ super(json, options)
46
+ this.changes = 'changes' in json ? json.changes : [json]
47
+ }
48
+
49
+ toJSON(): SerializedUndoMergeTranscriptsChange {
50
+ const { assembly, changedIds, changes, typeName } = this
51
+ if (changes.length === 1) {
52
+ const [{ transcriptsToRestore, parentFeatureId }] = changes
53
+
54
+ return {
55
+ typeName,
56
+ changedIds,
57
+ assembly,
58
+ transcriptsToRestore,
59
+ parentFeatureId,
60
+ }
61
+ }
62
+ return { typeName, changedIds, assembly, changes }
63
+ }
64
+
65
+ async executeOnServer(backend: ServerDataStore) {
66
+ const { featureModel, session } = backend
67
+ const { changes } = this
68
+ for (const change of changes) {
69
+ const { transcriptsToRestore, parentFeatureId } = change
70
+ if (transcriptsToRestore.length !== 2) {
71
+ throw new Error(
72
+ `Expected exactly two transcripts to restore. Got :${transcriptsToRestore.length}`,
73
+ )
74
+ }
75
+ const topLevelFeature = await featureModel
76
+ .findOne({ allIds: parentFeatureId })
77
+ .session(session)
78
+ .exec()
79
+ if (!topLevelFeature) {
80
+ throw new Error(`Could not find feature with ID "${parentFeatureId}"`)
81
+ }
82
+ const parentFeature = this.getFeatureFromId(
83
+ topLevelFeature,
84
+ parentFeatureId,
85
+ )
86
+ if (!parentFeature) {
87
+ throw new Error(
88
+ `Could not find feature with ID "${parentFeatureId}" in feature "${topLevelFeature._id.toString()}"`,
89
+ )
90
+ }
91
+ if (!parentFeature.children) {
92
+ parentFeature.children = new Map()
93
+ }
94
+ for (const transcript of transcriptsToRestore) {
95
+ this.addChild(parentFeature, transcript)
96
+ const childIds = this.getChildFeatureIds(transcript)
97
+ topLevelFeature.allIds.push(transcript._id, ...childIds)
98
+ }
99
+ await topLevelFeature.save()
100
+ }
101
+ }
102
+
103
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
104
+ throw new Error('executeOnLocalGFF3 not implemented')
105
+ }
106
+
107
+ async executeOnClient(dataStore: ClientDataStore) {
108
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
109
+ if (!dataStore) {
110
+ throw new Error('No data store')
111
+ }
112
+ const { changes } = this
113
+ for (const change of changes) {
114
+ const { transcriptsToRestore, parentFeatureId } = change
115
+ if (!parentFeatureId) {
116
+ throw new Error('Parent ID is missing')
117
+ }
118
+ const parentFeature = dataStore.getFeature(parentFeatureId)
119
+ if (!parentFeature) {
120
+ throw new Error(`Could not find parent feature "${parentFeatureId}"`)
121
+ }
122
+ // create an ID for the parent feature if it does not have one
123
+ if (!parentFeature.attributes.get('_id')) {
124
+ parentFeature.setAttribute('_id', [parentFeature._id])
125
+ }
126
+ for (const transcript of transcriptsToRestore) {
127
+ parentFeature.addChild(transcript)
128
+ }
129
+ }
130
+ }
131
+
132
+ getInverse() {
133
+ const { assembly, changedIds, changes, logger } = this
134
+ const inverseChangedIds = [...changedIds].reverse()
135
+ const inverseChanges = [...changes]
136
+ .reverse()
137
+ .map((undoMergeTranscriptsChange) => ({
138
+ firstTranscript: undoMergeTranscriptsChange.transcriptsToRestore[0],
139
+ secondTranscript: undoMergeTranscriptsChange.transcriptsToRestore[1],
140
+ parentFeatureId: undoMergeTranscriptsChange.parentFeatureId,
141
+ }))
142
+
143
+ return new MergeTranscriptsChange(
144
+ {
145
+ changedIds: inverseChangedIds,
146
+ typeName: 'MergeTranscriptsChange',
147
+ changes: inverseChanges,
148
+ assembly,
149
+ },
150
+ { logger },
151
+ )
152
+ }
153
+ }