@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
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # apollo-shared
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@apollo-annotation/shared",
3
+ "version": "0.1.11",
4
+ "main": "./dist/index.js",
5
+ "scripts": {
6
+ "start": "tsc --build --watch",
7
+ "build": "yarn clean && tsc --build",
8
+ "clean": "rimraf dist"
9
+ },
10
+ "dependencies": {
11
+ "@apollo-annotation/common": "^0.1.11",
12
+ "@apollo-annotation/mst": "^0.1.11",
13
+ "@apollo-annotation/schemas": "^0.1.11",
14
+ "@gmod/gff": "1.2.0",
15
+ "@gmod/indexedfasta": "^2.0.4",
16
+ "@jbrowse/core": "^2.7.0",
17
+ "bson-objectid": "^2.0.4",
18
+ "generic-filehandle": "^3.0.0",
19
+ "jwt-decode": "^3.1.2",
20
+ "regenerator-runtime": "^0.13.9",
21
+ "socket.io-client": "^4.5.4",
22
+ "tslib": "^2.3.1"
23
+ },
24
+ "devDependencies": {
25
+ "@nestjs/common": "^10.1.0",
26
+ "@nestjs/core": "^10.1.0",
27
+ "@types/node": "^18.14.2",
28
+ "@types/rimraf": "^3",
29
+ "mobx": "^6.6.1",
30
+ "mobx-state-tree": "^5.1.7",
31
+ "mongoose": "^6.12.0",
32
+ "rimraf": "^3.0.2",
33
+ "typescript": "^5.1.6"
34
+ },
35
+ "peerDependencies": {
36
+ "@mui/material": "^5.11.14",
37
+ "@mui/x-data-grid": "^7.0.0",
38
+ "mobx": "^6.6.1",
39
+ "mobx-react": "^7.2.1",
40
+ "mobx-state-tree": "^5.1.7",
41
+ "prop-types": "^15.8.1",
42
+ "react": "^18.2.0",
43
+ "react-dom": "^18.2.0",
44
+ "rxjs": "^7.4.0"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ }
49
+ }
@@ -0,0 +1,135 @@
1
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
2
+ /* eslint-disable @typescript-eslint/require-await */
3
+
4
+ import {
5
+ AssemblySpecificChange,
6
+ ChangeOptions,
7
+ ClientDataStore,
8
+ LocalGFF3DataStore,
9
+ SerializedAssemblySpecificChange,
10
+ ServerDataStore,
11
+ } from '@apollo-annotation/common'
12
+ import { GFF3Feature } from '@gmod/gff'
13
+
14
+ export interface SerializedAddAssemblyAndFeaturesFromFileChangeBase
15
+ extends SerializedAssemblySpecificChange {
16
+ typeName: 'AddAssemblyAndFeaturesFromFileChange'
17
+ }
18
+
19
+ export interface AddAssemblyAndFeaturesFromFileChangeDetails {
20
+ assemblyName: string
21
+ fileId: string
22
+ }
23
+
24
+ export interface SerializedAddAssemblyAndFeaturesFromFileChangeSingle
25
+ extends SerializedAddAssemblyAndFeaturesFromFileChangeBase,
26
+ AddAssemblyAndFeaturesFromFileChangeDetails {}
27
+
28
+ export interface SerializedAddAssemblyAndFeaturesFromFileChangeMultiple
29
+ extends SerializedAddAssemblyAndFeaturesFromFileChangeBase {
30
+ changes: AddAssemblyAndFeaturesFromFileChangeDetails[]
31
+ }
32
+
33
+ export type SerializedAddAssemblyAndFeaturesFromFileChange =
34
+ | SerializedAddAssemblyAndFeaturesFromFileChangeSingle
35
+ | SerializedAddAssemblyAndFeaturesFromFileChangeMultiple
36
+
37
+ export class AddAssemblyAndFeaturesFromFileChange extends AssemblySpecificChange {
38
+ typeName = 'AddAssemblyAndFeaturesFromFileChange' as const
39
+ changes: AddAssemblyAndFeaturesFromFileChangeDetails[]
40
+
41
+ constructor(
42
+ json: SerializedAddAssemblyAndFeaturesFromFileChange,
43
+ options?: ChangeOptions,
44
+ ) {
45
+ super(json, options)
46
+ this.changes = 'changes' in json ? json.changes : [json]
47
+ }
48
+
49
+ get notification(): string {
50
+ return `Assembly "${this.changes[0].assemblyName}" added successfully. To use it, please refresh the page.`
51
+ }
52
+
53
+ toJSON(): SerializedAddAssemblyAndFeaturesFromFileChange {
54
+ const { assembly, changes, typeName } = this
55
+ if (changes.length === 1) {
56
+ const [{ assemblyName, fileId }] = changes
57
+ return { typeName, assembly, assemblyName, fileId }
58
+ }
59
+ return { typeName, assembly, changes }
60
+ }
61
+
62
+ /**
63
+ * Applies the required change to database
64
+ * @param backend - parameters from backend
65
+ * @returns
66
+ */
67
+ async executeOnServer(backend: ServerDataStore) {
68
+ const { assemblyModel, fileModel, filesService, user } = backend
69
+ const { assembly, changes, logger } = this
70
+ for (const change of changes) {
71
+ const { assemblyName, fileId } = change
72
+
73
+ const { FILE_UPLOAD_FOLDER } = process.env
74
+ if (!FILE_UPLOAD_FOLDER) {
75
+ throw new Error('No FILE_UPLOAD_FOLDER found in .env file')
76
+ }
77
+ // Get file checksum
78
+ const fileDoc = await fileModel.findById(fileId).exec()
79
+ if (!fileDoc) {
80
+ throw new Error(`File "${fileId}" not found in Mongo`)
81
+ }
82
+ logger.debug?.(`FileId "${fileId}", checksum "${fileDoc.checksum}"`)
83
+
84
+ // Check and add new assembly
85
+ const assemblyDoc = await assemblyModel
86
+ .findOne({ name: assemblyName })
87
+ .exec()
88
+ if (assemblyDoc) {
89
+ throw new Error(`Assembly "${assemblyName}" already exists`)
90
+ }
91
+ // Add assembly
92
+ const [newAssemblyDoc] = await assemblyModel.create([
93
+ { _id: assembly, name: assemblyName, user, status: -1 },
94
+ ])
95
+ logger.debug?.(
96
+ `Added new assembly "${assemblyName}", docId "${newAssemblyDoc._id}"`,
97
+ )
98
+ logger.debug?.(`File type: "${fileDoc.type}"`)
99
+
100
+ // Add refSeqs
101
+ // We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
102
+ await this.addRefSeqIntoDb(
103
+ fileDoc,
104
+ newAssemblyDoc._id.toString(),
105
+ backend,
106
+ )
107
+
108
+ // Loop all features
109
+ const featureStream = filesService.parseGFF3(
110
+ filesService.getFileStream(fileDoc),
111
+ )
112
+ for await (const f of featureStream) {
113
+ const gff3Feature = f as GFF3Feature
114
+ logger.verbose?.(`ENTRY=${JSON.stringify(gff3Feature)}`)
115
+ // Add new feature into database
116
+ await this.addFeatureIntoDb(gff3Feature, backend)
117
+ }
118
+ }
119
+ }
120
+
121
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
122
+ throw new Error('executeOnLocalGFF3 not implemented')
123
+ }
124
+
125
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
126
+ async executeOnClient(_dataStore: ClientDataStore) {}
127
+
128
+ getInverse() {
129
+ const { assembly, changes, logger, typeName } = this
130
+ return new AddAssemblyAndFeaturesFromFileChange(
131
+ { typeName, changes, assembly },
132
+ { logger },
133
+ )
134
+ }
135
+ }
@@ -0,0 +1,145 @@
1
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
2
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
3
+
4
+ /* eslint-disable @typescript-eslint/require-await */
5
+ import {
6
+ AssemblySpecificChange,
7
+ ChangeOptions,
8
+ ClientDataStore,
9
+ LocalGFF3DataStore,
10
+ SerializedAssemblySpecificChange,
11
+ ServerDataStore,
12
+ } from '@apollo-annotation/common'
13
+ import { BgzipIndexedFasta, IndexedFasta } from '@gmod/indexedfasta'
14
+ import { RemoteFile } from 'generic-filehandle'
15
+
16
+ export interface SerializedAddAssemblyFromExternalChangeBase
17
+ extends SerializedAssemblySpecificChange {
18
+ typeName: 'AddAssemblyFromExternalChange'
19
+ }
20
+
21
+ export interface AddAssemblyFromExternalChangeDetails {
22
+ assemblyName: string
23
+ externalLocation: { fa: string; fai: string; gzi?: string }
24
+ }
25
+
26
+ export interface SerializedAddAssemblyFromExternalChangeSingle
27
+ extends SerializedAddAssemblyFromExternalChangeBase,
28
+ AddAssemblyFromExternalChangeDetails {}
29
+
30
+ export interface SerializedAddAssemblyFromExternalChangeMultiple
31
+ extends SerializedAddAssemblyFromExternalChangeBase {
32
+ changes: AddAssemblyFromExternalChangeDetails[]
33
+ }
34
+
35
+ export type SerializedAddAssemblyFromExternalChange =
36
+ | SerializedAddAssemblyFromExternalChangeSingle
37
+ | SerializedAddAssemblyFromExternalChangeMultiple
38
+
39
+ export class AddAssemblyFromExternalChange extends AssemblySpecificChange {
40
+ typeName = 'AddAssemblyFromExternalChange' as const
41
+ changes: AddAssemblyFromExternalChangeDetails[]
42
+
43
+ constructor(
44
+ json: SerializedAddAssemblyFromExternalChange,
45
+ options?: ChangeOptions,
46
+ ) {
47
+ super(json, options)
48
+ this.changes = 'changes' in json ? json.changes : [json]
49
+ }
50
+
51
+ get notification(): string {
52
+ return `Assembly "${this.changes[0].assemblyName}" added successfully. To use it, please refresh the page.`
53
+ }
54
+
55
+ toJSON(): SerializedAddAssemblyFromExternalChange {
56
+ const { assembly, changes, typeName } = this
57
+ if (changes.length === 1) {
58
+ const [{ assemblyName, externalLocation }] = changes
59
+ return { typeName, assembly, assemblyName, externalLocation }
60
+ }
61
+ return { typeName, assembly, changes }
62
+ }
63
+
64
+ /**
65
+ * Applies the required change to database
66
+ * @param backend - parameters from backend
67
+ * @returns
68
+ */
69
+ async executeOnServer(backend: ServerDataStore) {
70
+ const { assemblyModel, refSeqModel, user } = backend
71
+ const { assembly, changes, logger } = this
72
+ const { CHUNK_SIZE } = process.env
73
+ const customChunkSize = CHUNK_SIZE && Number(CHUNK_SIZE)
74
+
75
+ for (const change of changes) {
76
+ const { assemblyName, externalLocation } = change
77
+ const { fa, fai, gzi } = externalLocation
78
+ const sequenceAdapter = gzi
79
+ ? new BgzipIndexedFasta({
80
+ fasta: new RemoteFile(fa, { fetch }),
81
+ fai: new RemoteFile(fai, { fetch }),
82
+ gzi: new RemoteFile(gzi, { fetch }),
83
+ })
84
+ : new IndexedFasta({
85
+ fasta: new RemoteFile(fa, { fetch }),
86
+ fai: new RemoteFile(fai, { fetch }),
87
+ })
88
+ const allSequenceSizes = await sequenceAdapter.getSequenceSizes()
89
+
90
+ if (!allSequenceSizes) {
91
+ throw new Error('No data read from indexed fasta getSequenceSizes')
92
+ }
93
+
94
+ const assemblyDoc = await assemblyModel
95
+ .findOne({ name: assemblyName })
96
+ .exec()
97
+ if (assemblyDoc) {
98
+ throw new Error(`Assembly "${assemblyName}" already exists`)
99
+ }
100
+ const [newAssemblyDoc] = await assemblyModel.create([
101
+ {
102
+ _id: assembly,
103
+ name: assemblyName,
104
+ user,
105
+ status: -1,
106
+ externalLocation,
107
+ },
108
+ ])
109
+ logger.debug?.(
110
+ `Added new assembly "${assemblyName}", docId "${newAssemblyDoc._id}"`,
111
+ )
112
+
113
+ for (const sequenceName in allSequenceSizes) {
114
+ const [newRefSeqDoc] = await refSeqModel.create([
115
+ {
116
+ name: sequenceName,
117
+ assembly: newAssemblyDoc._id,
118
+ length: allSequenceSizes[sequenceName],
119
+ ...(customChunkSize ? { chunkSize: customChunkSize } : null),
120
+ user,
121
+ status: -1,
122
+ },
123
+ ])
124
+ logger.debug?.(
125
+ `Added new refSeq "${sequenceName}", docId "${newRefSeqDoc._id}"`,
126
+ )
127
+ }
128
+ }
129
+ }
130
+
131
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
132
+ throw new Error('executeOnLocalGFF3 not implemented')
133
+ }
134
+
135
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
136
+ async executeOnClient(_dataStore: ClientDataStore) {}
137
+
138
+ getInverse() {
139
+ const { assembly, changes, logger, typeName } = this
140
+ return new AddAssemblyFromExternalChange(
141
+ { typeName, changes, assembly },
142
+ { logger },
143
+ )
144
+ }
145
+ }
@@ -0,0 +1,125 @@
1
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
2
+ /* eslint-disable @typescript-eslint/require-await */
3
+ import {
4
+ AssemblySpecificChange,
5
+ ChangeOptions,
6
+ ClientDataStore,
7
+ LocalGFF3DataStore,
8
+ SerializedAssemblySpecificChange,
9
+ ServerDataStore,
10
+ } from '@apollo-annotation/common'
11
+
12
+ export interface SerializedAddAssemblyFromFileChangeBase
13
+ extends SerializedAssemblySpecificChange {
14
+ typeName: 'AddAssemblyFromFileChange'
15
+ }
16
+
17
+ export interface AddAssemblyFromFileChangeDetails {
18
+ assemblyName: string
19
+ fileId: string
20
+ }
21
+
22
+ export interface SerializedAddAssemblyFromFileChangeSingle
23
+ extends SerializedAddAssemblyFromFileChangeBase,
24
+ AddAssemblyFromFileChangeDetails {}
25
+
26
+ export interface SerializedAddAssemblyFromFileChangeMultiple
27
+ extends SerializedAddAssemblyFromFileChangeBase {
28
+ changes: AddAssemblyFromFileChangeDetails[]
29
+ }
30
+
31
+ export type SerializedAddAssemblyFromFileChange =
32
+ | SerializedAddAssemblyFromFileChangeSingle
33
+ | SerializedAddAssemblyFromFileChangeMultiple
34
+
35
+ export class AddAssemblyFromFileChange extends AssemblySpecificChange {
36
+ typeName = 'AddAssemblyFromFileChange' as const
37
+ changes: AddAssemblyFromFileChangeDetails[]
38
+
39
+ constructor(
40
+ json: SerializedAddAssemblyFromFileChange,
41
+ options?: ChangeOptions,
42
+ ) {
43
+ super(json, options)
44
+ this.changes = 'changes' in json ? json.changes : [json]
45
+ }
46
+
47
+ get notification(): string {
48
+ return `Assembly "${this.changes[0].assemblyName}" added successfully. To use it, please refresh the page.`
49
+ }
50
+
51
+ toJSON(): SerializedAddAssemblyFromFileChange {
52
+ const { assembly, changes, typeName } = this
53
+ if (changes.length === 1) {
54
+ const [{ assemblyName, fileId }] = changes
55
+ return { typeName, assembly, assemblyName, fileId }
56
+ }
57
+ return { typeName, assembly, changes }
58
+ }
59
+
60
+ /**
61
+ * Applies the required change to database
62
+ * @param backend - parameters from backend
63
+ * @returns
64
+ */
65
+ async executeOnServer(backend: ServerDataStore) {
66
+ const { assemblyModel, fileModel, user } = backend
67
+ const { assembly, changes, logger } = this
68
+
69
+ for (const change of changes) {
70
+ const { assemblyName, fileId } = change
71
+
72
+ const { FILE_UPLOAD_FOLDER } = process.env
73
+ if (!FILE_UPLOAD_FOLDER) {
74
+ throw new Error('No FILE_UPLOAD_FOLDER found in .env file')
75
+ }
76
+ // Get file checksum
77
+ const fileDoc = await fileModel.findById(fileId).exec()
78
+ if (!fileDoc) {
79
+ throw new Error(`File "${fileId}" not found in Mongo`)
80
+ }
81
+ logger.debug?.(`FileId "${fileId}", checksum "${fileDoc.checksum}"`)
82
+
83
+ // Check and add new assembly
84
+ const assemblyDoc = await assemblyModel
85
+ .findOne({ name: assemblyName })
86
+ .exec()
87
+ if (assemblyDoc) {
88
+ throw new Error(`Assembly "${assemblyName}" already exists`)
89
+ }
90
+ // Add assembly
91
+ const [newAssemblyDoc] = await assemblyModel.create([
92
+ { _id: assembly, name: assemblyName, user, status: -1 },
93
+ ])
94
+ logger.debug?.(
95
+ `Added new assembly "${assemblyName}", docId "${newAssemblyDoc._id}"`,
96
+ )
97
+ logger.debug?.(
98
+ `File type: "${fileDoc.type}", assemblyId: "${newAssemblyDoc._id}"`,
99
+ )
100
+
101
+ // Add refSeqs
102
+ // We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
103
+ await this.addRefSeqIntoDb(
104
+ fileDoc,
105
+ newAssemblyDoc._id.toString(),
106
+ backend,
107
+ )
108
+ }
109
+ }
110
+
111
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
112
+ throw new Error('executeOnLocalGFF3 not implemented')
113
+ }
114
+
115
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
116
+ async executeOnClient(_dataStore: ClientDataStore) {}
117
+
118
+ getInverse() {
119
+ const { assembly, changes, logger, typeName } = this
120
+ return new AddAssemblyFromFileChange(
121
+ { typeName, changes, assembly },
122
+ { logger },
123
+ )
124
+ }
125
+ }
@@ -0,0 +1,233 @@
1
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
+ /* eslint-disable @typescript-eslint/require-await */
4
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
5
+ import {
6
+ ChangeOptions,
7
+ ClientDataStore,
8
+ FeatureChange,
9
+ LocalGFF3DataStore,
10
+ SerializedFeatureChange,
11
+ ServerDataStore,
12
+ } from '@apollo-annotation/common'
13
+ import { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
14
+
15
+ import { DeleteFeatureChange } from './DeleteFeatureChange'
16
+
17
+ interface SerializedAddFeatureChangeBase extends SerializedFeatureChange {
18
+ typeName: 'AddFeatureChange'
19
+ }
20
+
21
+ export interface AddFeatureChangeDetails {
22
+ addedFeature: AnnotationFeatureSnapshot
23
+ parentFeatureId?: string // Parent feature to where feature will be added
24
+ copyFeature?: boolean // Are we copying or adding a new child feature
25
+ allIds?: string[]
26
+ }
27
+
28
+ interface SerializedAddFeatureChangeSingle
29
+ extends SerializedAddFeatureChangeBase,
30
+ AddFeatureChangeDetails {}
31
+
32
+ interface SerializedAddFeatureChangeMultiple
33
+ extends SerializedAddFeatureChangeBase {
34
+ changes: AddFeatureChangeDetails[]
35
+ }
36
+
37
+ type SerializedAddFeatureChange =
38
+ | SerializedAddFeatureChangeSingle
39
+ | SerializedAddFeatureChangeMultiple
40
+
41
+ export class AddFeatureChange extends FeatureChange {
42
+ typeName = 'AddFeatureChange' as const
43
+ changes: AddFeatureChangeDetails[]
44
+
45
+ constructor(json: SerializedAddFeatureChange, options?: ChangeOptions) {
46
+ super(json, options)
47
+ this.changes = 'changes' in json ? json.changes : [json]
48
+ }
49
+
50
+ toJSON(): SerializedAddFeatureChange {
51
+ const { assembly, changedIds, changes, typeName } = this
52
+ if (changes.length === 1) {
53
+ const [{ addedFeature, allIds, copyFeature, parentFeatureId }] = changes
54
+ return {
55
+ typeName,
56
+ changedIds,
57
+ assembly,
58
+ addedFeature,
59
+ parentFeatureId,
60
+ copyFeature,
61
+ allIds,
62
+ }
63
+ }
64
+ return { typeName, changedIds, assembly, changes }
65
+ }
66
+
67
+ /**
68
+ * Applies the required change to database
69
+ * @param backend - parameters from backend
70
+ * @returns
71
+ */
72
+ async executeOnServer(backend: ServerDataStore) {
73
+ const { assemblyModel, featureModel, refSeqModel, session, user } = backend
74
+ const { assembly, changes, logger } = this
75
+
76
+ const assemblyDoc = await assemblyModel
77
+ .findById(assembly)
78
+ .session(session)
79
+ .exec()
80
+ if (!assemblyDoc) {
81
+ const errMsg = `*** ERROR: Assembly with id "${assembly}" not found`
82
+ logger.error(errMsg)
83
+ throw new Error(errMsg)
84
+ }
85
+
86
+ let featureCnt = 0
87
+ logger.debug?.(`changes: ${JSON.stringify(changes)}`)
88
+
89
+ // Loop the changes
90
+ for (const change of changes) {
91
+ logger.debug?.(`change: ${JSON.stringify(change)}`)
92
+ const { addedFeature, allIds, copyFeature, parentFeatureId } = change
93
+ const { _id, refSeq } = addedFeature
94
+ const refSeqDoc = await refSeqModel
95
+ .findById(refSeq)
96
+ .session(session)
97
+ .exec()
98
+ if (!refSeqDoc) {
99
+ throw new Error(
100
+ `RefSeq was not found by assembly "${assembly}" and seq_id "${refSeq}" not found`,
101
+ )
102
+ }
103
+
104
+ // CopyFeature is called from CopyFeature.tsx
105
+ if (copyFeature) {
106
+ // Add into Mongo
107
+ const [newFeatureDoc] = await featureModel.create(
108
+ [{ ...addedFeature, allIds, status: -1, user }],
109
+ { session },
110
+ )
111
+ logger.debug?.(
112
+ `Copied feature, docId "${newFeatureDoc._id}" to assembly "${assembly}"`,
113
+ )
114
+ featureCnt++
115
+ } else {
116
+ addedFeature.gffId = _id // User added manually new feature so then gffId = _id
117
+ // Adding new child feature
118
+ if (parentFeatureId) {
119
+ const topLevelFeature = await featureModel
120
+ .findOne({ allIds: parentFeatureId })
121
+ .session(session)
122
+ .exec()
123
+ if (!topLevelFeature) {
124
+ throw new Error(
125
+ `Could not find feature with ID "${parentFeatureId}"`,
126
+ )
127
+ }
128
+ const parentFeature = this.getFeatureFromId(
129
+ topLevelFeature,
130
+ parentFeatureId,
131
+ )
132
+ if (!parentFeature) {
133
+ throw new Error(
134
+ `Could not find feature with ID "${parentFeatureId}" in feature "${topLevelFeature._id}"`,
135
+ )
136
+ }
137
+ if (!parentFeature.children) {
138
+ parentFeature.children = new Map()
139
+ }
140
+ if (!parentFeature.attributes?._id) {
141
+ let { attributes } = parentFeature
142
+ if (!attributes) {
143
+ attributes = {}
144
+ }
145
+ attributes = {
146
+ _id: [parentFeature._id.toString()],
147
+ ...JSON.parse(JSON.stringify(attributes)),
148
+ }
149
+ parentFeature.attributes = attributes
150
+ }
151
+ parentFeature.children.set(_id, {
152
+ allIds: [],
153
+ ...addedFeature,
154
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
155
+ // @ts-expect-error
156
+ _id,
157
+ })
158
+ // Child features should be sorted for click and drag of gene glyphs to work properly
159
+ parentFeature.children = new Map(
160
+ [...parentFeature.children.entries()].sort(
161
+ (a, b) => a[1].start - b[1].start,
162
+ ),
163
+ )
164
+ const childIds = this.getChildFeatureIds(addedFeature)
165
+ topLevelFeature.allIds.push(_id, ...childIds)
166
+ await topLevelFeature.save()
167
+ } else {
168
+ const childIds = this.getChildFeatureIds(addedFeature)
169
+ const allIdsV2 = [_id, ...childIds]
170
+ const [newFeatureDoc] = await featureModel.create(
171
+ [{ allIds: allIdsV2, status: 0, ...addedFeature }],
172
+ { session },
173
+ )
174
+ logger.verbose?.(`Added docId "${newFeatureDoc._id}"`)
175
+ }
176
+ }
177
+ featureCnt++
178
+ }
179
+ logger.debug?.(`Added ${featureCnt} new feature(s) into database.`)
180
+ }
181
+
182
+ async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
183
+ throw new Error('executeOnLocalGFF3 not implemented')
184
+ }
185
+
186
+ async executeOnClient(dataStore: ClientDataStore) {
187
+ if (!dataStore) {
188
+ throw new Error('No data store')
189
+ }
190
+ const { assembly, changes } = this
191
+ for (const change of changes) {
192
+ const { addedFeature, parentFeatureId } = change
193
+ if (parentFeatureId) {
194
+ const parentFeature = dataStore.getFeature(parentFeatureId)
195
+ if (!parentFeature) {
196
+ throw new Error(`Could not find parent feature "${parentFeatureId}"`)
197
+ }
198
+ // create an ID for the parent feature if it does not have one
199
+ if (!parentFeature.attributes.get('_id')) {
200
+ parentFeature.setAttribute('_id', [parentFeature._id])
201
+ }
202
+ parentFeature.addChild(addedFeature)
203
+ } else {
204
+ dataStore.addFeature(assembly, addedFeature)
205
+ }
206
+ }
207
+ }
208
+
209
+ getInverse() {
210
+ const { assembly, changedIds, changes, logger } = this
211
+ const inverseChangedIds = [...changedIds].reverse()
212
+ const inverseChanges = [...changes].reverse().map((addFeatureChange) => ({
213
+ deletedFeature: addFeatureChange.addedFeature,
214
+ parentFeatureId: addFeatureChange.parentFeatureId,
215
+ }))
216
+
217
+ return new DeleteFeatureChange(
218
+ {
219
+ changedIds: inverseChangedIds,
220
+ typeName: 'DeleteFeatureChange',
221
+ changes: inverseChanges,
222
+ assembly,
223
+ },
224
+ { logger },
225
+ )
226
+ }
227
+ }
228
+
229
+ export function isAddFeatureChange(
230
+ change: unknown,
231
+ ): change is AddFeatureChange {
232
+ return (change as AddFeatureChange).typeName === 'AddFeatureChange'
233
+ }