@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,120 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
import {
|
|
3
|
+
AssemblySpecificChange,
|
|
4
|
+
ChangeOptions,
|
|
5
|
+
ClientDataStore,
|
|
6
|
+
LocalGFF3DataStore,
|
|
7
|
+
SerializedAssemblySpecificChange,
|
|
8
|
+
ServerDataStore,
|
|
9
|
+
} from '@apollo-annotation/common'
|
|
10
|
+
import { GFF3Feature } from '@gmod/gff'
|
|
11
|
+
|
|
12
|
+
export interface SerializedAddFeaturesFromFileChangeBase
|
|
13
|
+
extends SerializedAssemblySpecificChange {
|
|
14
|
+
typeName: 'AddFeaturesFromFileChange'
|
|
15
|
+
deleteExistingFeatures?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AddFeaturesFromFileChangeDetails {
|
|
19
|
+
fileId: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SerializedAddFeaturesFromFileChangeSingle
|
|
23
|
+
extends SerializedAddFeaturesFromFileChangeBase,
|
|
24
|
+
AddFeaturesFromFileChangeDetails {}
|
|
25
|
+
|
|
26
|
+
export interface SerializedAddFeaturesFromFileChangeMultiple
|
|
27
|
+
extends SerializedAddFeaturesFromFileChangeBase {
|
|
28
|
+
changes: AddFeaturesFromFileChangeDetails[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SerializedAddFeaturesFromFileChange =
|
|
32
|
+
| SerializedAddFeaturesFromFileChangeSingle
|
|
33
|
+
| SerializedAddFeaturesFromFileChangeMultiple
|
|
34
|
+
|
|
35
|
+
export class AddFeaturesFromFileChange extends AssemblySpecificChange {
|
|
36
|
+
typeName = 'AddFeaturesFromFileChange' as const
|
|
37
|
+
changes: AddFeaturesFromFileChangeDetails[]
|
|
38
|
+
deleteExistingFeatures = false
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
json: SerializedAddFeaturesFromFileChange,
|
|
42
|
+
options?: ChangeOptions,
|
|
43
|
+
) {
|
|
44
|
+
super(json, options)
|
|
45
|
+
this.deleteExistingFeatures = json.deleteExistingFeatures ?? false
|
|
46
|
+
this.changes = 'changes' in json ? json.changes : [json]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/class-literal-property-style
|
|
50
|
+
get notification(): string {
|
|
51
|
+
return 'Features have been added. To see them, please refresh the page.'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
toJSON(): SerializedAddFeaturesFromFileChange {
|
|
55
|
+
const { assembly, changes, deleteExistingFeatures, typeName } = this
|
|
56
|
+
if (changes.length === 1) {
|
|
57
|
+
const [{ fileId }] = changes
|
|
58
|
+
return { typeName, assembly, fileId, deleteExistingFeatures }
|
|
59
|
+
}
|
|
60
|
+
return { typeName, assembly, changes, deleteExistingFeatures }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Applies the required change to database
|
|
65
|
+
* @param backend - parameters from backend
|
|
66
|
+
* @returns
|
|
67
|
+
*/
|
|
68
|
+
async executeOnServer(backend: ServerDataStore) {
|
|
69
|
+
const { fileModel, filesService } = backend
|
|
70
|
+
const { changes, deleteExistingFeatures, logger } = this
|
|
71
|
+
|
|
72
|
+
if (deleteExistingFeatures) {
|
|
73
|
+
await this.removeExistingFeatures(backend)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const change of changes) {
|
|
77
|
+
const { fileId } = change
|
|
78
|
+
|
|
79
|
+
const { FILE_UPLOAD_FOLDER } = process.env
|
|
80
|
+
if (!FILE_UPLOAD_FOLDER) {
|
|
81
|
+
throw new Error('No FILE_UPLOAD_FOLDER found in .env file')
|
|
82
|
+
}
|
|
83
|
+
// Get file checksum
|
|
84
|
+
const fileDoc = await fileModel.findById(fileId).exec()
|
|
85
|
+
if (!fileDoc) {
|
|
86
|
+
throw new Error(`File "${fileId}" not found in Mongo`)
|
|
87
|
+
}
|
|
88
|
+
logger.debug?.(`FileId "${fileId}", checksum "${fileDoc.checksum}"`)
|
|
89
|
+
|
|
90
|
+
// Read data from compressed file and parse the content
|
|
91
|
+
const featureStream = filesService.parseGFF3(
|
|
92
|
+
filesService.getFileStream(fileDoc),
|
|
93
|
+
)
|
|
94
|
+
for await (const f of featureStream) {
|
|
95
|
+
const gff3Feature = f as GFF3Feature
|
|
96
|
+
logger.verbose?.(`ENTRY=${JSON.stringify(gff3Feature)}`)
|
|
97
|
+
|
|
98
|
+
// Add new feature into database
|
|
99
|
+
// We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
|
|
100
|
+
await this.addFeatureIntoDb(gff3Feature, backend)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
logger.debug?.('New features added into database!')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
|
|
107
|
+
throw new Error('executeOnLocalGFF3 not implemented')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
111
|
+
async executeOnClient(_dataStore: ClientDataStore) {}
|
|
112
|
+
|
|
113
|
+
getInverse() {
|
|
114
|
+
const { assembly, changes, logger, typeName } = this
|
|
115
|
+
return new AddFeaturesFromFileChange(
|
|
116
|
+
{ typeName, changes, assembly },
|
|
117
|
+
{ logger },
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
2
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
5
|
+
import {
|
|
6
|
+
AssemblySpecificChange,
|
|
7
|
+
ClientDataStore,
|
|
8
|
+
LocalGFF3DataStore,
|
|
9
|
+
SerializedAssemblySpecificChange,
|
|
10
|
+
ServerDataStore,
|
|
11
|
+
} from '@apollo-annotation/common'
|
|
12
|
+
import { getSession } from '@jbrowse/core/util'
|
|
13
|
+
|
|
14
|
+
interface SerializedDeleteAssemblyChange
|
|
15
|
+
extends SerializedAssemblySpecificChange {
|
|
16
|
+
typeName: 'DeleteAssemblyChange'
|
|
17
|
+
}
|
|
18
|
+
export class DeleteAssemblyChange extends AssemblySpecificChange {
|
|
19
|
+
typeName = 'DeleteAssemblyChange' as const
|
|
20
|
+
|
|
21
|
+
get notification(): string {
|
|
22
|
+
return `Assembly "${this.assembly}" deleted successfully.`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
toJSON(): SerializedDeleteAssemblyChange {
|
|
26
|
+
const { assembly, typeName } = this
|
|
27
|
+
return { typeName, assembly }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Applies the required change to database
|
|
32
|
+
* @param backend - parameters from backend
|
|
33
|
+
* @returns
|
|
34
|
+
*/
|
|
35
|
+
async executeOnServer(backend: ServerDataStore) {
|
|
36
|
+
const {
|
|
37
|
+
assemblyModel,
|
|
38
|
+
featureModel,
|
|
39
|
+
refSeqChunkModel,
|
|
40
|
+
refSeqModel,
|
|
41
|
+
session,
|
|
42
|
+
} = backend
|
|
43
|
+
const { assembly, logger } = this
|
|
44
|
+
|
|
45
|
+
const assemblyDoc = await assemblyModel
|
|
46
|
+
.findById(assembly)
|
|
47
|
+
.session(session)
|
|
48
|
+
.exec()
|
|
49
|
+
if (!assemblyDoc) {
|
|
50
|
+
const errMsg = `*** ERROR: Assembly with id "${assembly}" not found`
|
|
51
|
+
logger.error(errMsg)
|
|
52
|
+
throw new Error(errMsg)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
|
|
56
|
+
|
|
57
|
+
// Get RefSeqs
|
|
58
|
+
const refSeqs = await refSeqModel.find({ assembly }).exec()
|
|
59
|
+
const refSeqIds = refSeqs.map((refSeq) => refSeq._id)
|
|
60
|
+
|
|
61
|
+
// Get and delete RefSeqChunks
|
|
62
|
+
await refSeqChunkModel.deleteMany({ refSeq: refSeqIds }).exec()
|
|
63
|
+
|
|
64
|
+
// Get and delete Features
|
|
65
|
+
await featureModel.deleteMany({ refSeq: refSeqIds }).exec()
|
|
66
|
+
|
|
67
|
+
// Delete RefSeqs and Assembly
|
|
68
|
+
await refSeqModel.deleteMany({ assembly }).exec()
|
|
69
|
+
await assemblyModel.findByIdAndDelete(assembly).exec()
|
|
70
|
+
|
|
71
|
+
logger.debug?.(`Assembly "${assembly}" deleted from database.`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
|
|
75
|
+
throw new Error('executeOnLocalGFF3 not implemented')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async executeOnClient(dataStore: ClientDataStore) {
|
|
79
|
+
const { assembly } = this
|
|
80
|
+
if (!dataStore) {
|
|
81
|
+
throw new Error('No data store')
|
|
82
|
+
}
|
|
83
|
+
const session = getSession(dataStore)
|
|
84
|
+
// If assemblyId is not present in client data store
|
|
85
|
+
if (dataStore.assemblies.has(assembly)) {
|
|
86
|
+
dataStore.deleteAssembly(assembly)
|
|
87
|
+
}
|
|
88
|
+
await session.removeAssembly?.(assembly)
|
|
89
|
+
// @ts-expect-error this isn't on the AbstractSessionModel
|
|
90
|
+
await session.removeSessionAssembly?.(assembly)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getInverse() {
|
|
94
|
+
const { assembly, logger } = this
|
|
95
|
+
return new DeleteAssemblyChange(
|
|
96
|
+
{ typeName: 'DeleteAssemblyChange', assembly },
|
|
97
|
+
{ logger },
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
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 { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
13
|
+
import { Feature } from '@apollo-annotation/schemas'
|
|
14
|
+
|
|
15
|
+
import { AddFeatureChange } from './AddFeatureChange'
|
|
16
|
+
|
|
17
|
+
interface SerializedDeleteFeatureChangeBase extends SerializedFeatureChange {
|
|
18
|
+
typeName: 'DeleteFeatureChange'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DeleteFeatureChangeDetails {
|
|
22
|
+
deletedFeature: AnnotationFeatureSnapshot
|
|
23
|
+
parentFeatureId?: string // Parent feature from where feature was deleted.
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SerializedDeleteFeatureChangeSingle
|
|
27
|
+
extends SerializedDeleteFeatureChangeBase,
|
|
28
|
+
DeleteFeatureChangeDetails {}
|
|
29
|
+
|
|
30
|
+
interface SerializedDeleteFeatureChangeMultiple
|
|
31
|
+
extends SerializedDeleteFeatureChangeBase {
|
|
32
|
+
changes: DeleteFeatureChangeDetails[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type SerializedDeleteFeatureChange =
|
|
36
|
+
| SerializedDeleteFeatureChangeSingle
|
|
37
|
+
| SerializedDeleteFeatureChangeMultiple
|
|
38
|
+
|
|
39
|
+
export class DeleteFeatureChange extends FeatureChange {
|
|
40
|
+
typeName = 'DeleteFeatureChange' as const
|
|
41
|
+
changes: DeleteFeatureChangeDetails[]
|
|
42
|
+
|
|
43
|
+
constructor(json: SerializedDeleteFeatureChange, options?: ChangeOptions) {
|
|
44
|
+
super(json, options)
|
|
45
|
+
this.changes = 'changes' in json ? json.changes : [json]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toJSON(): SerializedDeleteFeatureChange {
|
|
49
|
+
const { assembly, changedIds, changes, typeName } = this
|
|
50
|
+
if (changes.length === 1) {
|
|
51
|
+
const [{ deletedFeature, parentFeatureId }] = changes
|
|
52
|
+
return { typeName, changedIds, assembly, deletedFeature, parentFeatureId }
|
|
53
|
+
}
|
|
54
|
+
return { typeName, changedIds, assembly, changes }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Applies the required change to database
|
|
59
|
+
* @param backend - parameters from backend
|
|
60
|
+
* @returns
|
|
61
|
+
*/
|
|
62
|
+
async executeOnServer(backend: ServerDataStore) {
|
|
63
|
+
const { featureModel, session } = backend
|
|
64
|
+
const { changes, logger } = this
|
|
65
|
+
|
|
66
|
+
// Loop the changes
|
|
67
|
+
for (const change of changes) {
|
|
68
|
+
const { deletedFeature, parentFeatureId } = change
|
|
69
|
+
|
|
70
|
+
// Search feature
|
|
71
|
+
const featureDoc = await featureModel
|
|
72
|
+
.findOne({ allIds: deletedFeature._id })
|
|
73
|
+
.session(session)
|
|
74
|
+
.exec()
|
|
75
|
+
if (!featureDoc) {
|
|
76
|
+
const errMsg = `*** ERROR: The following featureId was not found in database ='${deletedFeature._id}'`
|
|
77
|
+
logger.error(errMsg)
|
|
78
|
+
throw new Error(errMsg)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check if feature is on top level, then simply delete the whole document (i.e. not just sub-feature inside document)
|
|
82
|
+
if (featureDoc._id.equals(deletedFeature._id)) {
|
|
83
|
+
if (parentFeatureId) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Feature "${deletedFeature._id}" is top-level, but received a parent feature ID`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
await featureModel.findByIdAndDelete(featureDoc._id)
|
|
89
|
+
logger.debug?.(
|
|
90
|
+
`Feature "${deletedFeature._id}" deleted from document "${featureDoc._id}". Whole document deleted.`,
|
|
91
|
+
)
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const deletedIds = this.findAndDeleteChildFeature(
|
|
96
|
+
featureDoc,
|
|
97
|
+
deletedFeature._id,
|
|
98
|
+
)
|
|
99
|
+
deletedIds.push(deletedFeature._id)
|
|
100
|
+
featureDoc.allIds = featureDoc.allIds.filter(
|
|
101
|
+
(id) => !deletedIds.includes(id),
|
|
102
|
+
)
|
|
103
|
+
// Save updated document in Mongo
|
|
104
|
+
featureDoc.markModified('children') // Mark as modified. Without this save() -method is not updating data in database
|
|
105
|
+
try {
|
|
106
|
+
await featureDoc.save()
|
|
107
|
+
} catch (error) {
|
|
108
|
+
logger.debug?.(`*** FAILED: ${error}`)
|
|
109
|
+
throw error
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
logger.debug?.(
|
|
113
|
+
`Feature "${deletedFeature._id}" deleted from document "${featureDoc._id}"`,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Delete feature's subfeatures that match an ID and return the IDs of any
|
|
120
|
+
* sub-subfeatures that were deleted
|
|
121
|
+
* @param feature -
|
|
122
|
+
* @param featureIdToDelete -
|
|
123
|
+
* @returns - list of deleted feature IDs
|
|
124
|
+
*/
|
|
125
|
+
findAndDeleteChildFeature(
|
|
126
|
+
feature: Feature,
|
|
127
|
+
featureIdToDelete: string,
|
|
128
|
+
): string[] {
|
|
129
|
+
if (!feature.children) {
|
|
130
|
+
throw new Error(`Feature ${feature._id} has no children`)
|
|
131
|
+
}
|
|
132
|
+
const { _id, children } = feature
|
|
133
|
+
const child = children.get(featureIdToDelete)
|
|
134
|
+
if (child) {
|
|
135
|
+
const deletedIds = this.getChildFeatureIds(child)
|
|
136
|
+
children.delete(featureIdToDelete)
|
|
137
|
+
return deletedIds
|
|
138
|
+
}
|
|
139
|
+
for (const [, childFeature] of children) {
|
|
140
|
+
try {
|
|
141
|
+
return this.findAndDeleteChildFeature(childFeature, featureIdToDelete)
|
|
142
|
+
} catch {
|
|
143
|
+
// pass
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new Error(`Feature "${featureIdToDelete}" not found in ${_id}`)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
|
|
151
|
+
throw new Error('executeOnLocalGFF3 not implemented')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async executeOnClient(dataStore: ClientDataStore) {
|
|
155
|
+
if (!dataStore) {
|
|
156
|
+
throw new Error('No data store')
|
|
157
|
+
}
|
|
158
|
+
for (const change of this.changes) {
|
|
159
|
+
const { deletedFeature, parentFeatureId } = change
|
|
160
|
+
if (parentFeatureId) {
|
|
161
|
+
const parentFeature = dataStore.getFeature(parentFeatureId)
|
|
162
|
+
if (!parentFeature) {
|
|
163
|
+
throw new Error(`Could not find parent feature "${parentFeatureId}"`)
|
|
164
|
+
}
|
|
165
|
+
parentFeature.deleteChild(deletedFeature._id)
|
|
166
|
+
} else {
|
|
167
|
+
if (dataStore.getFeature(deletedFeature._id)) {
|
|
168
|
+
dataStore.deleteFeature(deletedFeature._id)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getInverse() {
|
|
175
|
+
const { assembly, changedIds, changes, logger } = this
|
|
176
|
+
const inverseChangedIds = [...changedIds].reverse()
|
|
177
|
+
const inverseChanges = [...changes]
|
|
178
|
+
.reverse()
|
|
179
|
+
.map((deleteFeatuerChange) => ({
|
|
180
|
+
addedFeature: deleteFeatuerChange.deletedFeature,
|
|
181
|
+
parentFeatureId: deleteFeatuerChange.parentFeatureId,
|
|
182
|
+
}))
|
|
183
|
+
logger.debug?.(`INVERSE CHANGE '${JSON.stringify(inverseChanges)}'`)
|
|
184
|
+
return new AddFeatureChange(
|
|
185
|
+
{
|
|
186
|
+
changedIds: inverseChangedIds,
|
|
187
|
+
typeName: 'AddFeatureChange',
|
|
188
|
+
changes: inverseChanges,
|
|
189
|
+
assembly,
|
|
190
|
+
},
|
|
191
|
+
{ logger },
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function isDeleteFeatureChange(
|
|
197
|
+
change: unknown,
|
|
198
|
+
): change is DeleteFeatureChange {
|
|
199
|
+
return (change as DeleteFeatureChange).typeName === 'DeleteFeatureChange'
|
|
200
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
import {
|
|
3
|
+
Change,
|
|
4
|
+
ChangeOptions,
|
|
5
|
+
ClientDataStore,
|
|
6
|
+
LocalGFF3DataStore,
|
|
7
|
+
SerializedChange,
|
|
8
|
+
ServerDataStore,
|
|
9
|
+
} from '@apollo-annotation/common'
|
|
10
|
+
|
|
11
|
+
export interface SerializedDeleteUserChangeBase extends SerializedChange {
|
|
12
|
+
typeName: 'DeleteUserChange'
|
|
13
|
+
userId: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
17
|
+
export interface DeleteUserChangeDetails {}
|
|
18
|
+
|
|
19
|
+
interface SerializedDeleteUserChangeSingle
|
|
20
|
+
extends SerializedDeleteUserChangeBase,
|
|
21
|
+
DeleteUserChangeDetails {}
|
|
22
|
+
|
|
23
|
+
interface SerializedDeleteUserChangeMultiple
|
|
24
|
+
extends SerializedDeleteUserChangeBase {
|
|
25
|
+
changes: DeleteUserChangeDetails[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type SerializedDeleteUserChange =
|
|
29
|
+
| SerializedDeleteUserChangeSingle
|
|
30
|
+
| SerializedDeleteUserChangeMultiple
|
|
31
|
+
|
|
32
|
+
export class DeleteUserChange extends Change {
|
|
33
|
+
typeName = 'DeleteUserChange' as const
|
|
34
|
+
changes: DeleteUserChangeDetails[]
|
|
35
|
+
userId: string
|
|
36
|
+
|
|
37
|
+
constructor(json: SerializedDeleteUserChange, options?: ChangeOptions) {
|
|
38
|
+
super(json, options)
|
|
39
|
+
this.changes = 'changes' in json ? json.changes : [json]
|
|
40
|
+
this.userId = json.userId
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
toJSON(): SerializedDeleteUserChange {
|
|
44
|
+
const { typeName, userId } = this
|
|
45
|
+
return { typeName, userId }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async executeOnServer(backend: ServerDataStore) {
|
|
49
|
+
const { session, userModel } = backend
|
|
50
|
+
const { logger, userId } = this
|
|
51
|
+
const user = await userModel
|
|
52
|
+
.findOneAndDelete({ _id: userId })
|
|
53
|
+
.session(session)
|
|
54
|
+
.exec()
|
|
55
|
+
if (!user) {
|
|
56
|
+
const errMsg = `*** ERROR: User with id "${userId}" not found`
|
|
57
|
+
logger.error(errMsg)
|
|
58
|
+
throw new Error(errMsg)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
|
|
63
|
+
throw new Error('executeOnLocalGFF3 not implemented')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
67
|
+
async executeOnClient(_dataStore: ClientDataStore) {}
|
|
68
|
+
|
|
69
|
+
getInverse() {
|
|
70
|
+
const { logger, typeName, userId } = this
|
|
71
|
+
return new DeleteUserChange({ typeName, userId }, { logger })
|
|
72
|
+
// const inverseChangedIds = this.changedIds.slice().reverse()
|
|
73
|
+
// const inverseChanges = this.changes
|
|
74
|
+
// .slice()
|
|
75
|
+
// .reverse()
|
|
76
|
+
// .map((deleteUserChange) => ({
|
|
77
|
+
// addedUser: deleteUserChange.userId,
|
|
78
|
+
// }))
|
|
79
|
+
// this.logger.debug?.(`INVERSE CHANGE '${JSON.stringify(inverseChanges)}'`)
|
|
80
|
+
// // return new AddUserChange()
|
|
81
|
+
// }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -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 SerializedDiscontinuousLocationEndChangeBase
|
|
14
|
+
extends SerializedFeatureChange {
|
|
15
|
+
typeName: 'DiscontinuousLocationEndChange'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface DiscontinuousLocationEndChangeDetails {
|
|
19
|
+
featureId: string
|
|
20
|
+
oldEnd: number
|
|
21
|
+
newEnd: number
|
|
22
|
+
index: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface SerializedDiscontinuousLocationEndChangeSingle
|
|
26
|
+
extends SerializedDiscontinuousLocationEndChangeBase,
|
|
27
|
+
DiscontinuousLocationEndChangeDetails {}
|
|
28
|
+
|
|
29
|
+
interface SerializedDiscontinuousLocationEndChangeMultiple
|
|
30
|
+
extends SerializedDiscontinuousLocationEndChangeBase {
|
|
31
|
+
changes: DiscontinuousLocationEndChangeDetails[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type SerializedDiscontinuousLocationEndChange =
|
|
35
|
+
| SerializedDiscontinuousLocationEndChangeSingle
|
|
36
|
+
| SerializedDiscontinuousLocationEndChangeMultiple
|
|
37
|
+
|
|
38
|
+
export class DiscontinuousLocationEndChange extends FeatureChange {
|
|
39
|
+
typeName = 'DiscontinuousLocationEndChange' as const
|
|
40
|
+
changes: DiscontinuousLocationEndChangeDetails[]
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
json: SerializedDiscontinuousLocationEndChange,
|
|
44
|
+
options?: ChangeOptions,
|
|
45
|
+
) {
|
|
46
|
+
super(json, options)
|
|
47
|
+
this.changes = 'changes' in json ? json.changes : [json]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
toJSON(): SerializedDiscontinuousLocationEndChange {
|
|
51
|
+
const { assembly, changedIds, changes, typeName } = this
|
|
52
|
+
if (changes.length === 1) {
|
|
53
|
+
const [{ featureId, index, newEnd, oldEnd }] = changes
|
|
54
|
+
return {
|
|
55
|
+
typeName,
|
|
56
|
+
changedIds,
|
|
57
|
+
assembly,
|
|
58
|
+
featureId,
|
|
59
|
+
oldEnd,
|
|
60
|
+
newEnd,
|
|
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, newEnd, oldEnd: expectedOldEnd } = 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 "LocationEndChange" to change a feature end that does not have discontinuous locations'
|
|
96
|
+
logger.error(errMsg)
|
|
97
|
+
throw new Error(errMsg)
|
|
98
|
+
}
|
|
99
|
+
const oldEnd = feature.discontinuousLocations[index].end
|
|
100
|
+
if (oldEnd !== expectedOldEnd) {
|
|
101
|
+
const errMsg = `Location's current end value ${oldEnd} doesn't match with expected value ${expectedOldEnd}`
|
|
102
|
+
logger.error(errMsg)
|
|
103
|
+
throw new Error(errMsg)
|
|
104
|
+
}
|
|
105
|
+
const { start } = feature.discontinuousLocations[index]
|
|
106
|
+
if (newEnd <= start) {
|
|
107
|
+
const errMsg = `location end (${newEnd}) can't be smaller than location start (${start})`
|
|
108
|
+
logger.error(errMsg)
|
|
109
|
+
throw new Error(errMsg)
|
|
110
|
+
}
|
|
111
|
+
const nextLocation = feature.discontinuousLocations[index + 1]
|
|
112
|
+
if (nextLocation && newEnd >= nextLocation.start) {
|
|
113
|
+
const errMsg = `Location end (${newEnd}) can't be larger than the next location's start (${nextLocation.start})`
|
|
114
|
+
logger.error(errMsg)
|
|
115
|
+
throw new Error(errMsg)
|
|
116
|
+
}
|
|
117
|
+
feature.discontinuousLocations[index].end = newEnd
|
|
118
|
+
if (index === feature.discontinuousLocations.length - 1) {
|
|
119
|
+
feature.end = newEnd
|
|
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, newEnd } = this.changes[idx]
|
|
150
|
+
feature.setCDSDiscontinuousLocationEnd(newEnd, 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
|
+
oldEnd: change.newEnd,
|
|
160
|
+
newEnd: change.oldEnd,
|
|
161
|
+
index: change.index,
|
|
162
|
+
}))
|
|
163
|
+
return new DiscontinuousLocationEndChange(
|
|
164
|
+
{
|
|
165
|
+
changedIds: inverseChangedIds,
|
|
166
|
+
typeName,
|
|
167
|
+
changes: inverseChanges,
|
|
168
|
+
assembly,
|
|
169
|
+
},
|
|
170
|
+
{ logger },
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function isDiscontinuousLocationEndChange(
|
|
176
|
+
change: unknown,
|
|
177
|
+
): change is DiscontinuousLocationEndChange {
|
|
178
|
+
return (
|
|
179
|
+
(change as DiscontinuousLocationEndChange).typeName ===
|
|
180
|
+
'DiscontinuousLocationEndChange'
|
|
181
|
+
)
|
|
182
|
+
}
|