@apollo-annotation/shared 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.d.ts +3 -0
  2. package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.js +6 -5
  3. package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.js.map +1 -1
  4. package/dist/Changes/AddAssemblyFromFileChange.js +1 -1
  5. package/dist/Changes/AddAssemblyFromFileChange.js.map +1 -1
  6. package/dist/Changes/AddFeatureChange.js +14 -2
  7. package/dist/Changes/AddFeatureChange.js.map +1 -1
  8. package/dist/Changes/AddFeaturesFromFileChange.d.ts +3 -0
  9. package/dist/Changes/AddFeaturesFromFileChange.js +5 -2
  10. package/dist/Changes/AddFeaturesFromFileChange.js.map +1 -1
  11. package/dist/Changes/AddRefSeqAliasesChange.js +2 -7
  12. package/dist/Changes/AddRefSeqAliasesChange.js.map +1 -1
  13. package/dist/Changes/FromFileBaseChange.js +6 -6
  14. package/dist/Changes/FromFileBaseChange.js.map +1 -1
  15. package/dist/Changes/ImportJBrowseConfigChange.js +1 -1
  16. package/dist/Changes/ImportJBrowseConfigChange.js.map +1 -1
  17. package/dist/Changes/MergeExonsChange.d.ts +0 -1
  18. package/dist/Changes/MergeExonsChange.js +14 -30
  19. package/dist/Changes/MergeExonsChange.js.map +1 -1
  20. package/dist/Changes/MergeTranscriptsChange.d.ts +1 -3
  21. package/dist/Changes/MergeTranscriptsChange.js +55 -56
  22. package/dist/Changes/MergeTranscriptsChange.js.map +1 -1
  23. package/dist/Checks/CDSCheck.d.ts +1 -1
  24. package/dist/Checks/CDSCheck.js +12 -12
  25. package/dist/Checks/CDSCheck.js.map +1 -1
  26. package/dist/Checks/TranscriptCheck.d.ts +9 -0
  27. package/dist/Checks/TranscriptCheck.js +109 -0
  28. package/dist/Checks/TranscriptCheck.js.map +1 -0
  29. package/dist/Checks/index.d.ts +1 -0
  30. package/dist/Checks/index.js +1 -0
  31. package/dist/Checks/index.js.map +1 -1
  32. package/dist/Checks/util.d.ts +2 -0
  33. package/dist/Checks/util.js +15 -0
  34. package/dist/Checks/util.js.map +1 -0
  35. package/dist/GFF3/annotationFeatureToGFF3.js +3 -1
  36. package/dist/GFF3/annotationFeatureToGFF3.js.map +1 -1
  37. package/dist/GFF3/gff3ToAnnotationFeature.js +1 -1
  38. package/dist/GFF3/gff3ToAnnotationFeature.js.map +1 -1
  39. package/dist/GFF3/gff3ToAnnotationFeature.test.js +4 -4
  40. package/dist/GFF3/gff3ToAnnotationFeature.test.js.map +1 -1
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/dist/util.d.ts +3 -0
  43. package/dist/util.js +33 -0
  44. package/dist/util.js.map +1 -1
  45. package/package.json +9 -9
  46. package/src/Changes/AddAssemblyAndFeaturesFromFileChange.ts +8 -6
  47. package/src/Changes/AddAssemblyFromFileChange.ts +1 -1
  48. package/src/Changes/AddFeatureChange.ts +16 -2
  49. package/src/Changes/AddFeaturesFromFileChange.ts +6 -1
  50. package/src/Changes/AddRefSeqAliasesChange.ts +2 -8
  51. package/src/Changes/FromFileBaseChange.ts +6 -6
  52. package/src/Changes/ImportJBrowseConfigChange.ts +1 -1
  53. package/src/Changes/MergeExonsChange.ts +52 -36
  54. package/src/Changes/MergeTranscriptsChange.ts +82 -67
  55. package/src/Checks/CDSCheck.ts +15 -13
  56. package/src/Checks/TranscriptCheck.ts +138 -0
  57. package/src/Checks/index.ts +1 -0
  58. package/src/Checks/util.ts +13 -0
  59. package/src/GFF3/annotationFeatureToGFF3.ts +4 -1
  60. package/src/GFF3/gff3ToAnnotationFeature.test.ts +4 -4
  61. package/src/GFF3/gff3ToAnnotationFeature.ts +1 -1
  62. package/src/util.ts +37 -0
  63. package/test_data/example01.json +20 -20
  64. package/test_data/example02.json +38 -38
  65. package/test_data/example04.json +57 -57
  66. package/test_data/gene_representations.gff3 +1054 -276
package/dist/util.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  import { type Feature } from '@apollo-annotation/schemas';
2
+ import { type IKeyValueMap } from 'mobx';
2
3
  export declare function splitStringIntoChunks(input: string, chunkSize: number): string[];
3
4
  export declare function getPrintableId(feature: Feature): string;
5
+ export declare function attributesToRecords(attributes: IKeyValueMap<readonly string[] | undefined> | undefined): Record<string, string[] | undefined>;
6
+ export declare function stringifyAttributes(attributes: Record<string, string[] | undefined> | undefined): string;
package/dist/util.js CHANGED
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.splitStringIntoChunks = splitStringIntoChunks;
4
4
  exports.getPrintableId = getPrintableId;
5
+ exports.attributesToRecords = attributesToRecords;
6
+ exports.stringifyAttributes = stringifyAttributes;
5
7
  function splitStringIntoChunks(input, chunkSize) {
6
8
  const chunks = [];
7
9
  for (let i = 0; i < input.length; i += chunkSize) {
@@ -22,4 +24,35 @@ function getPrintableId(feature) {
22
24
  }
23
25
  return `_id: ${feature._id.toString()}`;
24
26
  }
27
+ function attributesToRecords(attributes) {
28
+ const records = {};
29
+ if (!attributes) {
30
+ return records;
31
+ }
32
+ for (const [key, value] of Object.entries(attributes)) {
33
+ records[key] = value?.slice();
34
+ }
35
+ return records;
36
+ }
37
+ function stringifyAttributes(attributes) {
38
+ if (!attributes) {
39
+ return '';
40
+ }
41
+ const str = [];
42
+ for (const [key, value] of Object.entries(attributes)) {
43
+ let attributeName = key;
44
+ if (attributeName.startsWith('gff_')) {
45
+ attributeName = attributeName.slice(4);
46
+ attributeName =
47
+ attributeName.charAt(0).toUpperCase() + attributeName.slice(1);
48
+ }
49
+ if (value) {
50
+ str.push(`${attributeName}=${value.join(',')}`);
51
+ }
52
+ else {
53
+ str.push(attributeName);
54
+ }
55
+ }
56
+ return encodeURIComponent(str.join(';'));
57
+ }
25
58
  //# sourceMappingURL=util.js.map
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;AAGA,sDAUC;AAED,wCAWC;AAvBD,SAAgB,qBAAqB,CACnC,KAAa,EACb,SAAiB;IAEjB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAA;QAC3C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAgB,cAAc,CAAC,OAAgB;IAC7C,MAAM,EAAE,GAAG,OAAuC,CAAA;IAClD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACrD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,MAAM,UAAU,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAA;IACxD,CAAC;IACD,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACzD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,QAAQ,UAAU,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAA;IAC5D,CAAC;IACD,OAAO,QAAQ,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAA;AACzC,CAAC"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;AAIA,sDAUC;AAED,wCAWC;AAED,kDAWC;AAED,kDAqBC;AA3DD,SAAgB,qBAAqB,CACnC,KAAa,EACb,SAAiB;IAEjB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAA;QAC3C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAgB,cAAc,CAAC,OAAgB;IAC7C,MAAM,EAAE,GAAG,OAAuC,CAAA;IAClD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACrD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,MAAM,UAAU,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAA;IACxD,CAAC;IACD,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACzD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,QAAQ,UAAU,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAA;IAC5D,CAAC;IACD,OAAO,QAAQ,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAA;AACzC,CAAC;AAED,SAAgB,mBAAmB,CACjC,UAAmE;IAEnE,MAAM,OAAO,GAAyC,EAAE,CAAA;IACxD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,OAAO,CAAA;IAChB,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,CAAA;IAC/B,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAgB,mBAAmB,CACjC,UAA4D;IAE5D,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,GAAG,GAAG,EAAE,CAAA;IACd,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,IAAI,aAAa,GAAG,GAAG,CAAA;QACvB,IAAI,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,aAAa,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACtC,aAAa;gBACX,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAClE,CAAC;QACD,IAAI,KAAK,EAAE,CAAC;YACV,GAAG,CAAC,IAAI,CAAC,GAAG,aAAa,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACjD,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IACD,OAAO,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAC1C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo-annotation/shared",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "main": "./dist/index.js",
5
5
  "scripts": {
6
6
  "build": "yarn clean && tsc --build",
@@ -10,12 +10,12 @@
10
10
  "test:ci": "NODE_V8_COVERAGE=./coverage glob -c \"tsx --test --test-reporter spec --experimental-test-coverage \" \"**/*.test.ts\""
11
11
  },
12
12
  "dependencies": {
13
- "@apollo-annotation/common": "^0.3.7",
14
- "@apollo-annotation/mst": "^0.3.7",
15
- "@apollo-annotation/schemas": "^0.3.7",
16
- "@gmod/gff": "1.2.0",
13
+ "@apollo-annotation/common": "^0.3.9",
14
+ "@apollo-annotation/mst": "^0.3.9",
15
+ "@apollo-annotation/schemas": "^0.3.9",
16
+ "@gmod/gff": "^2.0.0",
17
17
  "@gmod/indexedfasta": "^2.0.4",
18
- "@jbrowse/core": "^3.0.1",
18
+ "@jbrowse/core": "^3.6.5",
19
19
  "bson-objectid": "^2.0.4",
20
20
  "generic-filehandle": "^3.0.0",
21
21
  "jwt-decode": "^3.1.2",
@@ -25,7 +25,7 @@
25
25
  "@nestjs/common": "^10.1.0",
26
26
  "@nestjs/core": "^10.1.0",
27
27
  "@types/chai": "^4.3.19",
28
- "@types/node": "^18.14.2",
28
+ "@types/node": "^20.19.15",
29
29
  "chai": "^5.1.1",
30
30
  "chai-exclude": "^3.0.0",
31
31
  "glob": "^11.0.0",
@@ -39,8 +39,8 @@
39
39
  "typescript": "^5.5.3"
40
40
  },
41
41
  "peerDependencies": {
42
- "@mui/material": "^6.0.0",
43
- "@mui/x-data-grid": "^7.0.0",
42
+ "@mui/material": "^7.0.0",
43
+ "@mui/x-data-grid": "^8.0.0",
44
44
  "mobx": "^6.6.1",
45
45
  "mobx-react": "^7.2.1",
46
46
  "mobx-state-tree": "^5.4.0",
@@ -19,6 +19,7 @@ export interface SerializedAddAssemblyAndFeaturesFromFileChangeBase
19
19
  export interface AddAssemblyAndFeaturesFromFileChangeDetails {
20
20
  assemblyName: string
21
21
  fileIds: { fa: string }
22
+ parseOptions?: { bufferSize: number }
22
23
  }
23
24
 
24
25
  export interface SerializedAddAssemblyAndFeaturesFromFileChangeSingle
@@ -68,7 +69,7 @@ export class AddAssemblyAndFeaturesFromFileChange extends FromFileBaseChange {
68
69
  const { assemblyModel, checkModel, fileModel, filesService, user } = backend
69
70
  const { assembly, changes, logger } = this
70
71
  for (const change of changes) {
71
- const { assemblyName, fileIds } = change
72
+ const { assemblyName, fileIds, parseOptions } = change
72
73
  const fileId = fileIds.fa
73
74
 
74
75
  const { FILE_UPLOAD_FOLDER } = process.env
@@ -90,7 +91,8 @@ export class AddAssemblyAndFeaturesFromFileChange extends FromFileBaseChange {
90
91
  throw new Error(`Assembly "${assemblyName}" already exists`)
91
92
  }
92
93
  // get checks
93
- const checkDocs = await checkModel.find({ default: true }).exec()
94
+ const checkDocs = await checkModel.find({ isDefault: true }).exec()
95
+
94
96
  const checks = checkDocs.map((checkDoc) => checkDoc._id.toHexString())
95
97
  // Add assembly
96
98
  const [newAssemblyDoc] = await assemblyModel.create([
@@ -109,13 +111,13 @@ export class AddAssemblyAndFeaturesFromFileChange extends FromFileBaseChange {
109
111
  backend,
110
112
  )
111
113
 
112
- // Loop all features
113
- logger.debug?.(
114
- `**************** LOOPATAAN KAIKKI FEATURET SEURAAVAKSI File type: "${fileDoc.type}"`,
115
- )
114
+ const { bufferSize = 10_000 } = parseOptions ?? {}
116
115
  const featureStream = filesService.parseGFF3(
117
116
  filesService.getFileStream(fileDoc),
117
+ { bufferSize },
118
118
  )
119
+ // @ts-expect-error type is wrong here
120
+ // eslint-disable-next-line @typescript-eslint/await-thenable
119
121
  for await (const f of featureStream) {
120
122
  const gff3Feature = f as GFF3Feature
121
123
  logger.verbose?.(`ENTRY=${JSON.stringify(gff3Feature)}`)
@@ -128,7 +128,7 @@ export class AddAssemblyFromFileChange extends FromFileBaseChange {
128
128
  if (assemblyDoc) {
129
129
  throw new Error(`Assembly "${assemblyName}" already exists`)
130
130
  }
131
- const checkDocs = await checkModel.find({ default: true }).exec()
131
+ const checkDocs = await checkModel.find({ isDefault: true }).exec()
132
132
  const checks = checkDocs.map((checkDoc) => checkDoc._id.toHexString())
133
133
  const [newAssemblyDoc] = await assemblyModel.create([
134
134
  {
@@ -167,9 +167,23 @@ export class AddFeatureChange extends FeatureChange {
167
167
  for (const change of changes) {
168
168
  const { addedFeature, parentFeatureId } = change
169
169
  if (parentFeatureId) {
170
- const parentFeature = dataStore.getFeature(parentFeatureId)
170
+ let parentFeature = dataStore.getFeature(parentFeatureId)
171
+ // maybe the parent feature hasn't been loaded yet
171
172
  if (!parentFeature) {
172
- throw new Error(`Could not find parent feature "${parentFeatureId}"`)
173
+ await dataStore.loadFeatures([
174
+ {
175
+ assemblyName: assembly,
176
+ refName: addedFeature.refSeq,
177
+ start: addedFeature.min,
178
+ end: addedFeature.max,
179
+ },
180
+ ])
181
+ parentFeature = dataStore.getFeature(parentFeatureId)
182
+ if (!parentFeature) {
183
+ throw new Error(
184
+ `Could not find parent feature "${parentFeatureId}"`,
185
+ )
186
+ }
173
187
  }
174
188
  // create an ID for the parent feature if it does not have one
175
189
  if (!parentFeature.attributes.get('_id')) {
@@ -18,6 +18,7 @@ export interface SerializedAddFeaturesFromFileChangeBase
18
18
 
19
19
  export interface AddFeaturesFromFileChangeDetails {
20
20
  fileId: string
21
+ parseOptions?: { bufferSize: number }
21
22
  }
22
23
 
23
24
  export interface SerializedAddFeaturesFromFileChangeSingle
@@ -75,7 +76,7 @@ export class AddFeaturesFromFileChange extends FromFileBaseChange {
75
76
  }
76
77
 
77
78
  for (const change of changes) {
78
- const { fileId } = change
79
+ const { fileId, parseOptions } = change
79
80
 
80
81
  const { FILE_UPLOAD_FOLDER } = process.env
81
82
  if (!FILE_UPLOAD_FOLDER) {
@@ -89,10 +90,14 @@ export class AddFeaturesFromFileChange extends FromFileBaseChange {
89
90
  logger.debug?.(`FileId "${fileId}", checksum "${fileDoc.checksum}"`)
90
91
 
91
92
  // Read data from compressed file and parse the content
93
+ const { bufferSize = 10_000 } = parseOptions ?? {}
92
94
  const featureStream = filesService.parseGFF3(
93
95
  filesService.getFileStream(fileDoc),
96
+ { bufferSize },
94
97
  )
95
98
  let featureCount = 0
99
+ // @ts-expect-error type is wrong here
100
+ // eslint-disable-next-line @typescript-eslint/await-thenable
96
101
  for await (const f of featureStream) {
97
102
  const gff3Feature = f as GFF3Feature
98
103
 
@@ -35,21 +35,15 @@ export class AddRefSeqAliasesChange extends AssemblySpecificChange {
35
35
  if (!assembly) {
36
36
  throw new Error(`assembly ${this.assembly} not found`)
37
37
  }
38
- const sessionAliases = assembly.refNameAliases
39
- const sessionLCAliases = assembly.lowerCaseRefNameAliases
40
-
41
- if (!sessionAliases || !sessionLCAliases) {
42
- throw new Error('Session refNameAliases not found in assembly')
43
- }
38
+ const sessionAliases = assembly.refNameAliases ?? {}
44
39
 
45
40
  for (const refSeqAlias of this.refSeqAliases) {
46
41
  const { aliases, refName } = refSeqAlias
47
42
  for (const alias of aliases) {
48
43
  sessionAliases[alias] = refName
49
- sessionLCAliases[alias.toLowerCase()] = refName
50
44
  }
51
45
  }
52
- assembly.setRefNameAliases(sessionAliases, sessionLCAliases)
46
+ assembly.setRefNameAliases(sessionAliases)
53
47
  return Promise.resolve()
54
48
  }
55
49
 
@@ -1,7 +1,4 @@
1
- /* eslint-disable @typescript-eslint/restrict-plus-operands */
2
1
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4
- /* eslint-disable @typescript-eslint/no-unsafe-call */
5
2
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
6
3
  import {
7
4
  AssemblySpecificChange,
@@ -38,8 +35,11 @@ export abstract class FromFileBaseChange extends AssemblySpecificChange {
38
35
  let parsingStarted = false
39
36
  logger.debug?.('starting sequence stream')
40
37
  let lineCount = 0
38
+ const decoder = new TextDecoder()
39
+ // @ts-expect-error type is wrong here
40
+ // eslint-disable-next-line @typescript-eslint/await-thenable
41
41
  for await (const data of sequenceStream) {
42
- const chunk = data.toString()
42
+ const chunk = decoder.decode(data)
43
43
  lastLineIsIncomplete = !chunk.endsWith('\n')
44
44
  // chunk is small enough that you can split the whole thing into lines without having to make it into smaller chunks first.
45
45
  const lines = chunk.split(/\r?\n/)
@@ -48,9 +48,9 @@ export abstract class FromFileBaseChange extends AssemblySpecificChange {
48
48
  incompleteLine = ''
49
49
  }
50
50
  if (lastLineIsIncomplete) {
51
- incompleteLine = lines.pop() || ''
51
+ incompleteLine = lines.pop() ?? ''
52
52
  }
53
- for await (const line of lines) {
53
+ for (const line of lines) {
54
54
  lineCount++
55
55
  if (lineCount % 1_000_000 === 0) {
56
56
  logger.debug?.(`Processed ${lineCount} lines`)
@@ -75,7 +75,7 @@ export function filterJBrowseConfig(config: JBrowseConfig): JBrowseConfig {
75
75
  filteredConfig.plugins = plugins.filter((p) => p.name !== 'Apollo')
76
76
  }
77
77
  if (tracks) {
78
- filteredConfig.trackss = tracks.filter((t) => t.type !== 'ApolloTrack')
78
+ filteredConfig.tracks = tracks.filter((t) => t.type !== 'ApolloTrack')
79
79
  }
80
80
  return filteredConfig
81
81
  }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable unicorn/prefer-structured-clone */
1
2
  /* eslint-disable @typescript-eslint/require-await */
2
3
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
4
 
@@ -11,6 +12,8 @@ import {
11
12
  } from '@apollo-annotation/common'
12
13
  import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
13
14
 
15
+ import { attributesToRecords, stringifyAttributes } from '../util'
16
+
14
17
  import { findAndDeleteChildFeature } from './DeleteFeatureChange'
15
18
  import { UndoMergeExonsChange } from './UndoMergeExonsChange'
16
19
 
@@ -89,7 +92,15 @@ export class MergeExonsChange extends FeatureChange {
89
92
  }
90
93
  mergedExon.min = Math.min(firstExon.min, secondExon.min)
91
94
  mergedExon.max = Math.max(firstExon.max, secondExon.max)
92
- mergedExon.attributes = this.mergeAttributes(firstExon, secondExon)
95
+
96
+ const mergedAttributes: Record<string, string[]> = mergedExon.attributes
97
+ ? JSON.parse(JSON.stringify(mergedExon.attributes))
98
+ : {}
99
+ mergedAttributes.merged_with = [
100
+ stringifyAttributes(attributesToRecords(secondExon.attributes)),
101
+ ]
102
+ mergedExon.attributes = mergedAttributes
103
+
93
104
  const deletedIds = findAndDeleteChildFeature(
94
105
  topLevelFeature,
95
106
  secondExon._id,
@@ -123,11 +134,16 @@ export class MergeExonsChange extends FeatureChange {
123
134
  }
124
135
  mergedExon.setMin(Math.min(firstExon.min, secondExon.min))
125
136
  mergedExon.setMax(Math.max(firstExon.max, secondExon.max))
126
- const mergedAttrs = this.mergeAttributes(firstExon, secondExon)
127
- mergedExon.setAttributes(new Map())
128
- for (const key of Object.keys(mergedAttrs)) {
129
- mergedExon.setAttribute(key, mergedAttrs[key])
137
+
138
+ const mrg = mergedExon.attributes.get('merged_with')?.slice() ?? []
139
+ const mergedWith = stringifyAttributes(
140
+ attributesToRecords(secondExon.attributes),
141
+ )
142
+ if (!mrg.includes(mergedWith)) {
143
+ mrg.push(mergedWith)
130
144
  }
145
+ mergedExon.setAttribute('merged_with', mrg)
146
+
131
147
  mergedExon.parent?.deleteChild(secondExon._id)
132
148
  }
133
149
  }
@@ -151,35 +167,35 @@ export class MergeExonsChange extends FeatureChange {
151
167
  )
152
168
  }
153
169
 
154
- mergeAttributes(
155
- firstExon: AnnotationFeatureSnapshot,
156
- secondExon: AnnotationFeatureSnapshot,
157
- ): Record<string, string[]> {
158
- let mergedAttrs: Record<string, string[]> = {}
159
- if (firstExon.attributes) {
160
- // eslint-disable-next-line unicorn/prefer-structured-clone
161
- mergedAttrs = JSON.parse(JSON.stringify(firstExon.attributes))
162
- }
163
-
164
- if (secondExon.attributes) {
165
- // eslint-disable-next-line unicorn/prefer-structured-clone
166
- const attrs: Record<string, string[]> = JSON.parse(
167
- JSON.stringify(secondExon.attributes),
168
- )
169
- for (const key of Object.keys(attrs)) {
170
- if (key === '_id' || key === 'gff_id') {
171
- continue
172
- }
173
- if (!Object.keys(mergedAttrs).includes(key)) {
174
- mergedAttrs[key] = []
175
- }
176
- attrs[key].map((x) => {
177
- if (!mergedAttrs[key].includes(x)) {
178
- mergedAttrs[key].push(x)
179
- }
180
- })
181
- }
182
- }
183
- return mergedAttrs
184
- }
170
+ // mergeAttributes(
171
+ // firstExon: AnnotationFeatureSnapshot,
172
+ // secondExon: AnnotationFeatureSnapshot,
173
+ // ): Record<string, string[]> {
174
+ // let mergedAttrs: Record<string, string[]> = {}
175
+ // if (firstExon.attributes) {
176
+ // // eslint-disable-next-line unicorn/prefer-structured-clone
177
+ // mergedAttrs = JSON.parse(JSON.stringify(firstExon.attributes))
178
+ // }
179
+
180
+ // if (secondExon.attributes) {
181
+ // // eslint-disable-next-line unicorn/prefer-structured-clone
182
+ // const attrs: Record<string, string[]> = JSON.parse(
183
+ // JSON.stringify(secondExon.attributes),
184
+ // )
185
+ // for (const key of Object.keys(attrs)) {
186
+ // if (key === '_id' || key === 'gff_id') {
187
+ // continue
188
+ // }
189
+ // if (!Object.keys(mergedAttrs).includes(key)) {
190
+ // mergedAttrs[key] = []
191
+ // }
192
+ // attrs[key].map((x) => {
193
+ // if (!mergedAttrs[key].includes(x)) {
194
+ // mergedAttrs[key].push(x)
195
+ // }
196
+ // })
197
+ // }
198
+ // }
199
+ // return mergedAttrs
200
+ // }
185
201
  }
@@ -16,6 +16,9 @@ import {
16
16
  } from '@apollo-annotation/mst'
17
17
  import { type Feature } from '@apollo-annotation/schemas'
18
18
  import { doesIntersect2 } from '@jbrowse/core/util'
19
+ import { getSnapshot } from 'mobx-state-tree'
20
+
21
+ import { attributesToRecords, stringifyAttributes } from '../util'
19
22
 
20
23
  import { findAndDeleteChildFeature } from './DeleteFeatureChange'
21
24
  import { UndoMergeTranscriptsChange } from './UndoMergeTranscriptsChange'
@@ -42,6 +45,7 @@ interface SerializedMergeTranscriptsChangeMultiple
42
45
  export type SerializedMergeTranscriptsChange =
43
46
  | SerializedMergeTranscriptsChangeSingle
44
47
  | SerializedMergeTranscriptsChangeMultiple
48
+
45
49
  export class MergeTranscriptsChange extends FeatureChange {
46
50
  typeName = 'MergeTranscriptsChange' as const
47
51
  changes: MergeTranscriptsChangeDetails[]
@@ -50,6 +54,10 @@ export class MergeTranscriptsChange extends FeatureChange {
50
54
  super(json, options)
51
55
  this.changes = 'changes' in json ? json.changes : [json]
52
56
  }
57
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
58
+ get notification() {
59
+ return 'Transcripts successfully merged'
60
+ }
53
61
 
54
62
  toJSON(): SerializedMergeTranscriptsChange {
55
63
  const { assembly, changedIds, changes, typeName } = this
@@ -111,7 +119,21 @@ export class MergeTranscriptsChange extends FeatureChange {
111
119
  ) {
112
120
  firstTranscript.min = Math.min(firstTranscript.min, secondTranscript.min)
113
121
  firstTranscript.max = Math.max(firstTranscript.max, secondTranscript.max)
114
- this.mergeTranscriptAttributes(firstTranscript, secondTranscript)
122
+
123
+ const mergedAttributes: Record<string, string[]> =
124
+ firstTranscript.attributes
125
+ ? JSON.parse(JSON.stringify(firstTranscript.attributes))
126
+ : {}
127
+ if (secondTranscript.attributes) {
128
+ if (!Object.keys(mergedAttributes).includes('merged_with')) {
129
+ mergedAttributes.merged_with = []
130
+ }
131
+ mergedAttributes.merged_with.push(
132
+ stringifyAttributes(attributesToRecords(secondTranscript.attributes)),
133
+ )
134
+ }
135
+ firstTranscript.attributes = mergedAttributes
136
+
115
137
  if (secondTranscript.children) {
116
138
  for (const [, secondFeatureChild] of Object.entries(
117
139
  secondTranscript.children,
@@ -133,9 +155,13 @@ export class MergeTranscriptsChange extends FeatureChange {
133
155
  }
134
156
  let merged = false
135
157
  let mrgChild: Feature | undefined
136
- for (const [fKey, firstFeatureChild] of firstTranscript.children) {
158
+ let toDelete
159
+ for (const [, firstFeatureChild] of firstTranscript.children) {
137
160
  if (!merged || !mrgChild) {
161
+ toDelete = false
138
162
  mrgChild = firstFeatureChild
163
+ } else {
164
+ toDelete = true
139
165
  }
140
166
  if (
141
167
  mrgChild.type === secondFeatureChild.type &&
@@ -163,9 +189,36 @@ export class MergeTranscriptsChange extends FeatureChange {
163
189
  mrgChild.max,
164
190
  firstFeatureChild.max,
165
191
  )
166
- const mergedAttrs = this.mergeAttributes(mrgChild, secondFeatureChild)
167
- mrgChild.attributes = mergedAttrs
168
- firstTranscript.children.delete(fKey)
192
+
193
+ if (!mrgChild.attributes) {
194
+ mrgChild.attributes = {}
195
+ }
196
+
197
+ const mrgChildAttr: Record<string, string[]> = JSON.parse(
198
+ JSON.stringify(mrgChild.attributes),
199
+ )
200
+
201
+ if (!Object.keys(mrgChildAttr).includes('merged_with')) {
202
+ mrgChildAttr.merged_with = []
203
+ }
204
+ const mergedWithAttributes = mrgChildAttr.merged_with
205
+ mergedWithAttributes.push(
206
+ stringifyAttributes(
207
+ attributesToRecords(secondFeatureChild.attributes),
208
+ ),
209
+ )
210
+
211
+ if (toDelete) {
212
+ const recs: Record<string, string[] | undefined> =
213
+ firstFeatureChild.attributes
214
+ ? JSON.parse(JSON.stringify(firstFeatureChild.attributes))
215
+ : undefined
216
+ mergedWithAttributes.push(stringifyAttributes(recs))
217
+ firstTranscript.children.delete(firstFeatureChild._id.toString())
218
+ }
219
+
220
+ mrgChildAttr.merged_with = [...new Set(mergedWithAttributes)]
221
+ mrgChild.attributes = mrgChildAttr
169
222
  merged = true
170
223
  }
171
224
  }
@@ -215,7 +268,16 @@ export class MergeTranscriptsChange extends FeatureChange {
215
268
  firstTranscript.setMin(Math.min(firstTranscript.min, secondTranscript.min))
216
269
  firstTranscript.setMax(Math.max(firstTranscript.max, secondTranscript.max))
217
270
 
218
- this.mergeTranscriptAttributes(firstTranscript, secondTranscript)
271
+ const mrg = firstTranscript.attributes.get('merged_with')?.slice() ?? []
272
+ const mergedWith = stringifyAttributes(
273
+ attributesToRecords(secondTranscript.attributes),
274
+ )
275
+
276
+ if (!mrg.includes(mergedWith)) {
277
+ // executeOnClient runs twice (?!) so avoid adding this key again
278
+ mrg.push(mergedWith)
279
+ }
280
+ firstTranscript.setAttribute('merged_with', mrg)
219
281
 
220
282
  if (secondTranscript.children) {
221
283
  for (const [, secondFeatureChild] of Object.entries(
@@ -269,15 +331,20 @@ export class MergeTranscriptsChange extends FeatureChange {
269
331
  Math.max(secondFeatureChild.max, mrgChild.max, firstFeatureChild.max),
270
332
  )
271
333
 
272
- const mergedAttrs = this.mergeAttributes(mrgChild, secondFeatureChild)
273
- Object.entries(mergedAttrs).map(([key, value]) => {
274
- if (mrgChild) {
275
- mrgChild.setAttribute(key, value)
276
- }
277
- })
334
+ const mergedWithAttributes =
335
+ mrgChild.attributes.get('merged_with')?.slice() ?? []
336
+ mergedWithAttributes.push(
337
+ stringifyAttributes(
338
+ attributesToRecords(secondFeatureChild.attributes),
339
+ ),
340
+ )
278
341
  if (toDelete) {
342
+ mergedWithAttributes.push(
343
+ stringifyAttributes(getSnapshot(firstFeatureChild).attributes),
344
+ )
279
345
  firstTranscript.deleteChild(firstFeatureChild._id)
280
346
  }
347
+ mrgChild.setAttribute('merged_with', [...new Set(mergedWithAttributes)])
281
348
  merged = true
282
349
  }
283
350
  }
@@ -288,71 +355,19 @@ export class MergeTranscriptsChange extends FeatureChange {
288
355
  })
289
356
  }
290
357
 
291
- if (!(merged && mrgChild)) {
358
+ if (merged && mrgChild) {
359
+ firstTranscript.addChild(getSnapshot(mrgChild))
360
+ } else {
292
361
  // This secondFeatureChild has no overlap with any feature in the
293
362
  // receiving transcript so we add it as it is to the receiving transcript
294
363
  firstTranscript.addChild(secondFeatureChild)
295
364
  }
296
365
  }
297
366
 
298
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
299
- isAnnotationFeature(obj: any): obj is AnnotationFeature {
300
- return (
301
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
302
- typeof obj.setMin === 'function' &&
303
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
304
- typeof obj.setMax === 'function' &&
305
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
306
- typeof obj.addChild === 'function' &&
307
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
308
- typeof obj.deleteChild === 'function'
309
- )
310
- }
311
-
312
367
  async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
313
368
  throw new Error('executeOnLocalGFF3 not implemented')
314
369
  }
315
370
 
316
- /* Merge attributes from source into destination */
317
- mergeAttributes(
318
- destination: Feature | AnnotationFeature,
319
- source: AnnotationFeatureSnapshot,
320
- ): Record<string, string[]> {
321
- const destAttrs: Record<string, string[]> = destination.attributes
322
- ? JSON.parse(JSON.stringify(destination.attributes))
323
- : {}
324
- if (source.attributes) {
325
- const sourceAttrs: Record<string, string[]> = JSON.parse(
326
- JSON.stringify(source.attributes),
327
- )
328
- Object.entries(sourceAttrs).map(([key, value]) => {
329
- if (!(key in destAttrs)) {
330
- destAttrs[key] = []
331
- }
332
- value.map((x) => {
333
- if (!destAttrs[key].includes(x)) {
334
- destAttrs[key].push(x)
335
- }
336
- })
337
- })
338
- }
339
- return destAttrs
340
- }
341
-
342
- mergeTranscriptAttributes(
343
- firstTranscript: Feature | AnnotationFeature,
344
- secondTranscript: AnnotationFeatureSnapshot,
345
- ) {
346
- const txAttrs = this.mergeAttributes(firstTranscript, secondTranscript)
347
- if (this.isAnnotationFeature(firstTranscript)) {
348
- Object.entries(txAttrs).map(([key, value]) => {
349
- firstTranscript.setAttribute(key, value)
350
- })
351
- } else {
352
- firstTranscript.attributes = txAttrs
353
- }
354
- }
355
-
356
371
  getInverse() {
357
372
  const { assembly, changedIds, changes, logger } = this
358
373
  const inverseChangedIds = [...changedIds].reverse()