@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.
- package/README.md +1 -0
- package/package.json +49 -0
- package/src/Changes/AddAssemblyAndFeaturesFromFileChange.ts +135 -0
- package/src/Changes/AddAssemblyFromExternalChange.ts +145 -0
- package/src/Changes/AddAssemblyFromFileChange.ts +125 -0
- package/src/Changes/AddFeatureChange.ts +233 -0
- package/src/Changes/AddFeaturesFromFileChange.ts +120 -0
- package/src/Changes/DeleteAssemblyChange.ts +100 -0
- package/src/Changes/DeleteFeatureChange.ts +200 -0
- package/src/Changes/DeleteUserChange.ts +83 -0
- package/src/Changes/DiscontinuousLocationEndChange.ts +182 -0
- package/src/Changes/DiscontinuousLocationStartChange.ts +182 -0
- package/src/Changes/FeatureAttributeChange.ts +164 -0
- package/src/Changes/LocationEndChange.ts +175 -0
- package/src/Changes/LocationStartChange.ts +175 -0
- package/src/Changes/StrandChange.ts +159 -0
- package/src/Changes/TypeChange.ts +159 -0
- package/src/Changes/UserChange.ts +82 -0
- package/src/Changes/index.ts +52 -0
- package/src/Checks/CDSCheck.ts +275 -0
- package/src/Checks/index.ts +1 -0
- package/src/Common/index.ts +1 -0
- package/src/Common/jwtPayload.ts +25 -0
- package/src/Messages.ts +32 -0
- package/src/Operations/GetAssembliesOperation.ts +28 -0
- package/src/Operations/GetFeaturesOperation.ts +58 -0
- package/src/Operations/index.ts +6 -0
- package/src/Validations/CoreValidation.ts +37 -0
- package/src/Validations/ParentChildValidation.ts +88 -0
- package/src/Validations/Validation.ts +55 -0
- package/src/Validations/ValidationSet.ts +106 -0
- package/src/Validations/index.ts +4 -0
- package/src/Validations/soSequenceTypes.ts +1865 -0
- package/src/index.ts +7 -0
- package/src/util.ts +109 -0
- 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
|
+
}
|