@apollo-annotation/shared 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.
Files changed (36) hide show
  1. package/README.md +1 -0
  2. package/package.json +49 -0
  3. package/src/Changes/AddAssemblyAndFeaturesFromFileChange.ts +135 -0
  4. package/src/Changes/AddAssemblyFromExternalChange.ts +145 -0
  5. package/src/Changes/AddAssemblyFromFileChange.ts +125 -0
  6. package/src/Changes/AddFeatureChange.ts +233 -0
  7. package/src/Changes/AddFeaturesFromFileChange.ts +120 -0
  8. package/src/Changes/DeleteAssemblyChange.ts +100 -0
  9. package/src/Changes/DeleteFeatureChange.ts +200 -0
  10. package/src/Changes/DeleteUserChange.ts +83 -0
  11. package/src/Changes/DiscontinuousLocationEndChange.ts +182 -0
  12. package/src/Changes/DiscontinuousLocationStartChange.ts +182 -0
  13. package/src/Changes/FeatureAttributeChange.ts +164 -0
  14. package/src/Changes/LocationEndChange.ts +175 -0
  15. package/src/Changes/LocationStartChange.ts +175 -0
  16. package/src/Changes/StrandChange.ts +159 -0
  17. package/src/Changes/TypeChange.ts +159 -0
  18. package/src/Changes/UserChange.ts +82 -0
  19. package/src/Changes/index.ts +52 -0
  20. package/src/Checks/CDSCheck.ts +275 -0
  21. package/src/Checks/index.ts +1 -0
  22. package/src/Common/index.ts +1 -0
  23. package/src/Common/jwtPayload.ts +25 -0
  24. package/src/Messages.ts +32 -0
  25. package/src/Operations/GetAssembliesOperation.ts +28 -0
  26. package/src/Operations/GetFeaturesOperation.ts +58 -0
  27. package/src/Operations/index.ts +6 -0
  28. package/src/Validations/CoreValidation.ts +37 -0
  29. package/src/Validations/ParentChildValidation.ts +88 -0
  30. package/src/Validations/Validation.ts +55 -0
  31. package/src/Validations/ValidationSet.ts +106 -0
  32. package/src/Validations/index.ts +4 -0
  33. package/src/Validations/soSequenceTypes.ts +1865 -0
  34. package/src/index.ts +7 -0
  35. package/src/util.ts +109 -0
  36. package/tsconfig.json +19 -0
@@ -0,0 +1,182 @@
1
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
3
+ /* eslint-disable @typescript-eslint/require-await */
4
+ import {
5
+ ChangeOptions,
6
+ ClientDataStore,
7
+ FeatureChange,
8
+ LocalGFF3DataStore,
9
+ SerializedFeatureChange,
10
+ ServerDataStore,
11
+ } from '@apollo-annotation/common'
12
+
13
+ interface SerializedDiscontinuousLocationStartChangeBase
14
+ extends SerializedFeatureChange {
15
+ typeName: 'DiscontinuousLocationStartChange'
16
+ }
17
+
18
+ interface DiscontinuousLocationStartChangeDetails {
19
+ featureId: string
20
+ oldStart: number
21
+ newStart: number
22
+ index: number
23
+ }
24
+
25
+ interface SerializedDiscontinuousLocationStartChangeSingle
26
+ extends SerializedDiscontinuousLocationStartChangeBase,
27
+ DiscontinuousLocationStartChangeDetails {}
28
+
29
+ interface SerializedDiscontinuousLocationStartChangeMultiple
30
+ extends SerializedDiscontinuousLocationStartChangeBase {
31
+ changes: DiscontinuousLocationStartChangeDetails[]
32
+ }
33
+
34
+ type SerializedDiscontinuousLocationStartChange =
35
+ | SerializedDiscontinuousLocationStartChangeSingle
36
+ | SerializedDiscontinuousLocationStartChangeMultiple
37
+
38
+ export class DiscontinuousLocationStartChange extends FeatureChange {
39
+ typeName = 'DiscontinuousLocationStartChange' as const
40
+ changes: DiscontinuousLocationStartChangeDetails[]
41
+
42
+ constructor(
43
+ json: SerializedDiscontinuousLocationStartChange,
44
+ options?: ChangeOptions,
45
+ ) {
46
+ super(json, options)
47
+ this.changes = 'changes' in json ? json.changes : [json]
48
+ }
49
+
50
+ toJSON(): SerializedDiscontinuousLocationStartChange {
51
+ const { assembly, changedIds, changes, typeName } = this
52
+ if (changes.length === 1) {
53
+ const [{ featureId, index, newStart, oldStart }] = changes
54
+ return {
55
+ typeName,
56
+ changedIds,
57
+ assembly,
58
+ featureId,
59
+ oldStart,
60
+ newStart,
61
+ index,
62
+ }
63
+ }
64
+ return { typeName, changedIds, assembly, changes }
65
+ }
66
+
67
+ async executeOnServer(backend: ServerDataStore) {
68
+ const { featureModel, session } = backend
69
+ const { changes, logger } = this
70
+ for (const change of changes) {
71
+ const { featureId, index, newStart, oldStart: expectedOldStart } = change
72
+ const topLevelFeature = await featureModel
73
+ .findOne({ allIds: featureId })
74
+ .session(session)
75
+ .exec()
76
+
77
+ if (!topLevelFeature) {
78
+ const errMsg = `ERROR: The following featureId was not found in database ='${featureId}'`
79
+ logger.error(errMsg)
80
+ throw new Error(errMsg)
81
+ }
82
+
83
+ const feature = this.getFeatureFromId(topLevelFeature, featureId)
84
+ if (!feature) {
85
+ const errMsg = 'ERROR when searching feature by featureId'
86
+ logger.error(errMsg)
87
+ throw new Error(errMsg)
88
+ }
89
+ logger.debug?.(`*** Found feature: ${JSON.stringify(feature)}`)
90
+ if (
91
+ !feature.discontinuousLocations ||
92
+ feature.discontinuousLocations.length === 0
93
+ ) {
94
+ const errMsg =
95
+ 'Must use "LocationStartChange" to change a feature start that does not have discontinuous locations'
96
+ logger.error(errMsg)
97
+ throw new Error(errMsg)
98
+ }
99
+ const oldStart = feature.discontinuousLocations[index].start
100
+ if (oldStart !== expectedOldStart) {
101
+ const errMsg = `Location's current start value ${oldStart} doesn't match with expected value ${expectedOldStart}`
102
+ logger.error(errMsg)
103
+ throw new Error(errMsg)
104
+ }
105
+ const { end } = feature.discontinuousLocations[index]
106
+ if (newStart >= end) {
107
+ const errMsg = `location start (${newStart}) can't be larger than location end (${end})`
108
+ logger.error(errMsg)
109
+ throw new Error(errMsg)
110
+ }
111
+ const previousLocation = feature.discontinuousLocations[index - 1]
112
+ if (previousLocation && newStart <= previousLocation.end) {
113
+ const errMsg = `Location start (${newStart}) can't be larger than the previous location's end (${previousLocation.end})`
114
+ logger.error(errMsg)
115
+ throw new Error(errMsg)
116
+ }
117
+ feature.discontinuousLocations[index].start = newStart
118
+ if (index === 0) {
119
+ feature.start = newStart
120
+ }
121
+
122
+ try {
123
+ if (topLevelFeature._id.equals(feature._id)) {
124
+ topLevelFeature.markModified('discontinuousLocations')
125
+ } else {
126
+ topLevelFeature.markModified('children')
127
+ }
128
+ await topLevelFeature.save()
129
+ } catch (error) {
130
+ logger.debug?.(`*** FAILED: ${error}`)
131
+ throw error
132
+ }
133
+ }
134
+ }
135
+
136
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
137
+ throw new Error('executeOnLocalGFF3 not implemented')
138
+ }
139
+
140
+ async executeOnClient(dataStore: ClientDataStore) {
141
+ if (!dataStore) {
142
+ throw new Error('No data store')
143
+ }
144
+ for (const [idx, changedId] of this.changedIds.entries()) {
145
+ const feature = dataStore.getFeature(changedId)
146
+ if (!feature) {
147
+ throw new Error(`Could not find feature with identifier "${changedId}"`)
148
+ }
149
+ const { index, newStart } = this.changes[idx]
150
+ feature.setCDSDiscontinuousLocationStart(newStart, index)
151
+ }
152
+ }
153
+
154
+ getInverse() {
155
+ const { assembly, changedIds, changes, logger, typeName } = this
156
+ const inverseChangedIds = [...changedIds].reverse()
157
+ const inverseChanges = [...changes].reverse().map((change) => ({
158
+ featureId: change.featureId,
159
+ oldStart: change.newStart,
160
+ newStart: change.oldStart,
161
+ index: change.index,
162
+ }))
163
+ return new DiscontinuousLocationStartChange(
164
+ {
165
+ changedIds: inverseChangedIds,
166
+ typeName,
167
+ changes: inverseChanges,
168
+ assembly,
169
+ },
170
+ { logger },
171
+ )
172
+ }
173
+ }
174
+
175
+ export function isDiscontinuousLocationStartChange(
176
+ change: unknown,
177
+ ): change is DiscontinuousLocationStartChange {
178
+ return (
179
+ (change as DiscontinuousLocationStartChange).typeName ===
180
+ 'DiscontinuousLocationStartChange'
181
+ )
182
+ }
@@ -0,0 +1,164 @@
1
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
2
+ /* eslint-disable @typescript-eslint/require-await */
3
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
+ import {
5
+ ChangeOptions,
6
+ ClientDataStore,
7
+ FeatureChange,
8
+ LocalGFF3DataStore,
9
+ SerializedFeatureChange,
10
+ ServerDataStore,
11
+ } from '@apollo-annotation/common'
12
+ import { Feature, FeatureDocument } from '@apollo-annotation/schemas'
13
+
14
+ interface SerializedFeatureAttributeChangeBase extends SerializedFeatureChange {
15
+ typeName: 'FeatureAttributeChange'
16
+ }
17
+
18
+ export interface FeatureAttributeChangeDetails {
19
+ featureId: string
20
+ attributes: Record<string, string[]>
21
+ }
22
+
23
+ interface SerializedFeatureAttributeChangeSingle
24
+ extends SerializedFeatureAttributeChangeBase,
25
+ FeatureAttributeChangeDetails {}
26
+
27
+ interface SerializedFeatureAttributeChangeMultiple
28
+ extends SerializedFeatureAttributeChangeBase {
29
+ changes: FeatureAttributeChangeDetails[]
30
+ }
31
+
32
+ type SerializedFeatureAttributeChange =
33
+ | SerializedFeatureAttributeChangeSingle
34
+ | SerializedFeatureAttributeChangeMultiple
35
+
36
+ export class FeatureAttributeChange extends FeatureChange {
37
+ typeName = 'FeatureAttributeChange' as const
38
+ changes: FeatureAttributeChangeDetails[]
39
+
40
+ constructor(json: SerializedFeatureAttributeChange, options?: ChangeOptions) {
41
+ super(json, options)
42
+ this.changes = 'changes' in json ? json.changes : [json]
43
+ }
44
+
45
+ toJSON(): SerializedFeatureAttributeChange {
46
+ const { assembly, changedIds, changes, typeName } = this
47
+ if (changes.length === 1) {
48
+ const [{ attributes, featureId }] = changes
49
+ return { typeName, changedIds, assembly, featureId, attributes }
50
+ }
51
+ return { typeName, changedIds, assembly, changes }
52
+ }
53
+
54
+ /**
55
+ * Applies the required change to database
56
+ * @param backend - parameters from backend
57
+ * @returns
58
+ */
59
+ async executeOnServer(backend: ServerDataStore) {
60
+ const { featureModel, session } = backend
61
+ const { changes, logger } = this
62
+
63
+ const featuresForChanges: {
64
+ feature: Feature
65
+ topLevelFeature: FeatureDocument
66
+ }[] = []
67
+ // Loop the changes and check that all features are found
68
+ for (const change of changes) {
69
+ const { featureId } = change
70
+
71
+ // Search correct feature
72
+ const topLevelFeature = await featureModel
73
+ .findOne({ allIds: featureId })
74
+ .session(session)
75
+ .exec()
76
+
77
+ if (!topLevelFeature) {
78
+ const errMsg = `*** ERROR: The following featureId was not found in database ='${featureId}'`
79
+ logger.error(errMsg)
80
+ throw new Error(errMsg)
81
+ // throw new NotFoundException(errMsg) -- This is causing runtime error because Exception comes from @nestjs/common!!!
82
+ }
83
+ logger.debug?.(`*** Feature found: ${JSON.stringify(topLevelFeature)}`)
84
+
85
+ const foundFeature = this.getFeatureFromId(topLevelFeature, featureId)
86
+ if (!foundFeature) {
87
+ const errMsg = 'ERROR when searching feature by featureId'
88
+ logger.error(errMsg)
89
+ throw new Error(errMsg)
90
+ }
91
+ logger.debug?.(`*** Found feature: ${JSON.stringify(foundFeature)}`)
92
+ featuresForChanges.push({ feature: foundFeature, topLevelFeature })
93
+ }
94
+
95
+ // Let's update objects
96
+ for (const [idx, change] of changes.entries()) {
97
+ const { attributes } = change
98
+ const { feature, topLevelFeature } = featuresForChanges[idx]
99
+ feature.attributes = attributes
100
+ if (topLevelFeature._id.equals(feature._id)) {
101
+ topLevelFeature.markModified('attributes') // Mark as modified. Without this save() -method is not updating data in database
102
+ } else {
103
+ topLevelFeature.markModified('children') // Mark as modified. Without this save() -method is not updating data in database
104
+ }
105
+
106
+ try {
107
+ await topLevelFeature.save()
108
+ } catch (error) {
109
+ logger.debug?.(`*** FAILED: ${error}`)
110
+ throw error
111
+ }
112
+ logger.debug?.(
113
+ `*** Feature attributes modified (added, edited or deleted), docId: ${JSON.stringify(
114
+ topLevelFeature,
115
+ )}`,
116
+ )
117
+ }
118
+ }
119
+
120
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
121
+ throw new Error('applyToLocalGFF3 not implemented')
122
+ }
123
+
124
+ async executeOnClient(dataStore: ClientDataStore) {
125
+ if (!dataStore) {
126
+ throw new Error('No data store')
127
+ }
128
+ for (const [idx, changedId] of this.changedIds.entries()) {
129
+ const feature = dataStore.getFeature(changedId)
130
+ if (!feature) {
131
+ throw new Error(`Could not find feature with identifier "${changedId}"`)
132
+ }
133
+ feature.setAttributes(
134
+ new Map(Object.entries(this.changes[idx].attributes)),
135
+ )
136
+ }
137
+ }
138
+
139
+ getInverse() {
140
+ const { assembly, changedIds, changes, logger } = this
141
+ const inverseChangedIds = [...changedIds].reverse()
142
+ const inverseChanges = [...changes].reverse().map((oneChange) => ({
143
+ featureId: oneChange.featureId,
144
+ attributes: oneChange.attributes,
145
+ }))
146
+ return new FeatureAttributeChange(
147
+ {
148
+ changedIds: inverseChangedIds,
149
+ typeName: 'FeatureAttributeChange',
150
+ changes: inverseChanges,
151
+ assembly,
152
+ },
153
+ { logger },
154
+ )
155
+ }
156
+ }
157
+
158
+ export function isFeatureAttributeChange(
159
+ change: unknown,
160
+ ): change is FeatureAttributeChange {
161
+ return (
162
+ (change as FeatureAttributeChange).typeName === 'FeatureAttributeChange'
163
+ )
164
+ }
@@ -0,0 +1,175 @@
1
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
2
+ /* eslint-disable @typescript-eslint/require-await */
3
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
+ import {
5
+ ChangeOptions,
6
+ ClientDataStore,
7
+ FeatureChange,
8
+ LocalGFF3DataStore,
9
+ SerializedFeatureChange,
10
+ ServerDataStore,
11
+ } from '@apollo-annotation/common'
12
+ import { Feature, FeatureDocument } from '@apollo-annotation/schemas'
13
+
14
+ interface SerializedLocationEndChangeBase extends SerializedFeatureChange {
15
+ typeName: 'LocationEndChange'
16
+ }
17
+
18
+ export interface LocationEndChangeDetails {
19
+ featureId: string
20
+ oldEnd: number
21
+ newEnd: number
22
+ }
23
+
24
+ interface SerializedLocationEndChangeSingle
25
+ extends SerializedLocationEndChangeBase,
26
+ LocationEndChangeDetails {}
27
+
28
+ interface SerializedLocationEndChangeMultiple
29
+ extends SerializedLocationEndChangeBase {
30
+ changes: LocationEndChangeDetails[]
31
+ }
32
+
33
+ type SerializedLocationEndChange =
34
+ | SerializedLocationEndChangeSingle
35
+ | SerializedLocationEndChangeMultiple
36
+
37
+ export class LocationEndChange extends FeatureChange {
38
+ typeName = 'LocationEndChange' as const
39
+ changes: LocationEndChangeDetails[]
40
+
41
+ constructor(json: SerializedLocationEndChange, options?: ChangeOptions) {
42
+ super(json, options)
43
+ this.changes = 'changes' in json ? json.changes : [json]
44
+ }
45
+
46
+ toJSON(): SerializedLocationEndChange {
47
+ const { assembly, changedIds, changes, typeName } = this
48
+ if (changes.length === 1) {
49
+ const [{ featureId, newEnd, oldEnd }] = changes
50
+ return { typeName, changedIds, assembly, featureId, oldEnd, newEnd }
51
+ }
52
+ return { typeName, changedIds, assembly, changes }
53
+ }
54
+
55
+ /**
56
+ * Applies the required change to database
57
+ * @param backend - parameters from backend
58
+ * @returns
59
+ */
60
+ async executeOnServer(backend: ServerDataStore) {
61
+ const { featureModel, session } = backend
62
+ const { changes, logger } = this
63
+ const featuresForChanges: {
64
+ feature: Feature
65
+ topLevelFeature: FeatureDocument
66
+ }[] = []
67
+ // Let's first check that all features are found and those old values match with expected ones. We do this just to be sure that all changes can be done.
68
+ for (const change of changes) {
69
+ const { featureId, oldEnd } = change
70
+
71
+ // Search correct feature
72
+ const topLevelFeature = await featureModel
73
+ .findOne({ allIds: featureId })
74
+ .session(session)
75
+ .exec()
76
+
77
+ if (!topLevelFeature) {
78
+ const errMsg = `*** ERROR: The following featureId was not found in database ='${featureId}'`
79
+ logger.error(errMsg)
80
+ throw new Error(errMsg)
81
+ // throw new NotFoundException(errMsg) -- This is causing runtime error because Exception comes from @nestjs/common!!!
82
+ }
83
+ logger.debug?.(`*** Feature found: ${JSON.stringify(topLevelFeature)}`)
84
+
85
+ const foundFeature = this.getFeatureFromId(topLevelFeature, featureId)
86
+ if (!foundFeature) {
87
+ const errMsg = 'ERROR when searching feature by featureId'
88
+ logger.error(errMsg)
89
+ throw new Error(errMsg)
90
+ }
91
+ logger.debug?.(`*** Found feature: ${JSON.stringify(foundFeature)}`)
92
+ if (
93
+ foundFeature.discontinuousLocations &&
94
+ foundFeature.discontinuousLocations.length > 0
95
+ ) {
96
+ const errMsg =
97
+ 'Must use "DiscontinuousLocationEndChange" to change a feature end that has discontinuous locations'
98
+ logger.error(errMsg)
99
+ throw new Error(errMsg)
100
+ }
101
+ if (foundFeature.end !== oldEnd) {
102
+ const errMsg = `*** ERROR: Feature's current end value ${foundFeature.end} doesn't match with expected value ${oldEnd}`
103
+ logger.error(errMsg)
104
+ throw new Error(errMsg)
105
+ }
106
+ featuresForChanges.push({ feature: foundFeature, topLevelFeature })
107
+ }
108
+
109
+ // Let's update objects.
110
+ for (const [idx, change] of changes.entries()) {
111
+ const { newEnd } = change
112
+ const { feature, topLevelFeature } = featuresForChanges[idx]
113
+ feature.end = newEnd
114
+ if (topLevelFeature._id.equals(feature._id)) {
115
+ topLevelFeature.markModified('end') // Mark as modified. Without this save() -method is not updating data in database
116
+ } else {
117
+ topLevelFeature.markModified('children') // Mark as modified. Without this save() -method is not updating data in database
118
+ }
119
+
120
+ try {
121
+ await topLevelFeature.save()
122
+ } catch (error) {
123
+ logger.debug?.(`*** FAILED: ${error}`)
124
+ throw error
125
+ }
126
+ logger.debug?.(
127
+ `*** Object updated in Mongo. New object: ${JSON.stringify(
128
+ topLevelFeature,
129
+ )}`,
130
+ )
131
+ }
132
+ }
133
+
134
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
135
+ throw new Error('executeOnLocalGFF3 not implemented')
136
+ }
137
+
138
+ async executeOnClient(dataStore: ClientDataStore) {
139
+ if (!dataStore) {
140
+ throw new Error('No data store')
141
+ }
142
+ for (const [idx, changedId] of this.changedIds.entries()) {
143
+ const feature = dataStore.getFeature(changedId)
144
+ if (!feature) {
145
+ throw new Error(`Could not find feature with identifier "${changedId}"`)
146
+ }
147
+ feature.setEnd(this.changes[idx].newEnd)
148
+ }
149
+ }
150
+
151
+ getInverse() {
152
+ const { assembly, changedIds, changes, logger, typeName } = this
153
+ const inverseChangedIds = [...changedIds].reverse()
154
+ const inverseChanges = [...changes].reverse().map((endChange) => ({
155
+ featureId: endChange.featureId,
156
+ oldEnd: endChange.newEnd,
157
+ newEnd: endChange.oldEnd,
158
+ }))
159
+ return new LocationEndChange(
160
+ {
161
+ changedIds: inverseChangedIds,
162
+ typeName,
163
+ changes: inverseChanges,
164
+ assembly,
165
+ },
166
+ { logger },
167
+ )
168
+ }
169
+ }
170
+
171
+ export function isLocationEndChange(
172
+ change: unknown,
173
+ ): change is LocationEndChange {
174
+ return (change as LocationEndChange).typeName === 'LocationEndChange'
175
+ }
@@ -0,0 +1,175 @@
1
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
2
+ /* eslint-disable @typescript-eslint/require-await */
3
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
+ import {
5
+ ChangeOptions,
6
+ ClientDataStore,
7
+ FeatureChange,
8
+ LocalGFF3DataStore,
9
+ SerializedFeatureChange,
10
+ ServerDataStore,
11
+ } from '@apollo-annotation/common'
12
+ import { Feature, FeatureDocument } from '@apollo-annotation/schemas'
13
+
14
+ interface SerializedLocationStartChangeBase extends SerializedFeatureChange {
15
+ typeName: 'LocationStartChange'
16
+ }
17
+
18
+ interface LocationStartChangeDetails {
19
+ featureId: string
20
+ oldStart: number
21
+ newStart: number
22
+ }
23
+
24
+ interface SerializedLocationStartChangeSingle
25
+ extends SerializedLocationStartChangeBase,
26
+ LocationStartChangeDetails {}
27
+
28
+ interface SerializedLocationStartChangeMultiple
29
+ extends SerializedLocationStartChangeBase {
30
+ changes: LocationStartChangeDetails[]
31
+ }
32
+
33
+ type SerializedLocationStartChange =
34
+ | SerializedLocationStartChangeSingle
35
+ | SerializedLocationStartChangeMultiple
36
+
37
+ export class LocationStartChange extends FeatureChange {
38
+ typeName = 'LocationStartChange' as const
39
+ changes: LocationStartChangeDetails[]
40
+
41
+ constructor(json: SerializedLocationStartChange, options?: ChangeOptions) {
42
+ super(json, options)
43
+ this.changes = 'changes' in json ? json.changes : [json]
44
+ }
45
+
46
+ toJSON(): SerializedLocationStartChange {
47
+ const { assembly, changedIds, changes, typeName } = this
48
+ if (changes.length === 1) {
49
+ const [{ featureId, newStart, oldStart }] = changes
50
+ return { typeName, changedIds, assembly, featureId, oldStart, newStart }
51
+ }
52
+ return { typeName, changedIds, assembly, changes }
53
+ }
54
+
55
+ /**
56
+ * Applies the required change to database
57
+ * @param backend - parameters from backend
58
+ * @returns
59
+ */
60
+ async executeOnServer(backend: ServerDataStore) {
61
+ const { featureModel, session } = backend
62
+ const { changes, logger } = this
63
+ const featuresForChanges: {
64
+ feature: Feature
65
+ topLevelFeature: FeatureDocument
66
+ }[] = []
67
+ // Let's first check that all features are found and those old values match with expected ones. We do this just to be sure that all changes can be done.
68
+ for (const change of changes) {
69
+ const { featureId, oldStart } = change
70
+
71
+ // Search correct feature
72
+ const topLevelFeature = await featureModel
73
+ .findOne({ allIds: featureId })
74
+ .session(session)
75
+ .exec()
76
+
77
+ if (!topLevelFeature) {
78
+ const errMsg = `*** ERROR: The following featureId was not found in database ='${featureId}'`
79
+ logger.error(errMsg)
80
+ throw new Error(errMsg)
81
+ // throw new NotFoundException(errMsg) -- This is causing runtime error because Exception comes from @nestjs/common!!!
82
+ }
83
+ logger.debug?.(`*** Feature found: ${JSON.stringify(topLevelFeature)}`)
84
+
85
+ const foundFeature = this.getFeatureFromId(topLevelFeature, featureId)
86
+ if (!foundFeature) {
87
+ const errMsg = 'ERROR when searching feature by featureId'
88
+ logger.error(errMsg)
89
+ throw new Error(errMsg)
90
+ }
91
+ logger.debug?.(`*** Found feature: ${JSON.stringify(foundFeature)}`)
92
+ if (
93
+ foundFeature.discontinuousLocations &&
94
+ foundFeature.discontinuousLocations.length > 0
95
+ ) {
96
+ const errMsg =
97
+ 'Must use "DiscontinuousLocationStartChange" to change a feature start that has discontinuous locations'
98
+ logger.error(errMsg)
99
+ throw new Error(errMsg)
100
+ }
101
+ if (foundFeature.start !== oldStart) {
102
+ const errMsg = `*** ERROR: Feature's current start value ${foundFeature.start} doesn't match with expected value ${oldStart}`
103
+ logger.error(errMsg)
104
+ throw new Error(errMsg)
105
+ }
106
+ featuresForChanges.push({ feature: foundFeature, topLevelFeature })
107
+ }
108
+
109
+ // Let's update objects.
110
+ for (const [idx, change] of changes.entries()) {
111
+ const { newStart } = change
112
+ const { feature, topLevelFeature } = featuresForChanges[idx]
113
+ feature.start = newStart
114
+ if (topLevelFeature._id.equals(feature._id)) {
115
+ topLevelFeature.markModified('start') // Mark as modified. Without this save() -method is not updating data in database
116
+ } else {
117
+ topLevelFeature.markModified('children') // Mark as modified. Without this save() -method is not updating data in database
118
+ }
119
+
120
+ try {
121
+ await topLevelFeature.save()
122
+ } catch (error) {
123
+ logger.debug?.(`*** FAILED: ${error}`)
124
+ throw error
125
+ }
126
+ logger.debug?.(
127
+ `*** Object updated in Mongo. New object: ${JSON.stringify(
128
+ topLevelFeature,
129
+ )}`,
130
+ )
131
+ }
132
+ }
133
+
134
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
135
+ throw new Error('executeOnLocalGFF3 not implemented')
136
+ }
137
+
138
+ async executeOnClient(dataStore: ClientDataStore) {
139
+ if (!dataStore) {
140
+ throw new Error('No data store')
141
+ }
142
+ for (const [idx, changedId] of this.changedIds.entries()) {
143
+ const feature = dataStore.getFeature(changedId)
144
+ if (!feature) {
145
+ throw new Error(`Could not find feature with identifier "${changedId}"`)
146
+ }
147
+ feature.setStart(this.changes[idx].newStart)
148
+ }
149
+ }
150
+
151
+ getInverse() {
152
+ const { assembly, changedIds, changes, logger, typeName } = this
153
+ const inverseChangedIds = [...changedIds].reverse()
154
+ const inverseChanges = [...changes].reverse().map((startChange) => ({
155
+ featureId: startChange.featureId,
156
+ oldStart: startChange.newStart,
157
+ newStart: startChange.oldStart,
158
+ }))
159
+ return new LocationStartChange(
160
+ {
161
+ changedIds: inverseChangedIds,
162
+ typeName,
163
+ changes: inverseChanges,
164
+ assembly,
165
+ },
166
+ { logger },
167
+ )
168
+ }
169
+ }
170
+
171
+ export function isLocationStartChange(
172
+ change: unknown,
173
+ ): change is LocationStartChange {
174
+ return (change as LocationStartChange).typeName === 'LocationStartChange'
175
+ }