@apollo-annotation/jbrowse-plugin-apollo 0.1.0

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 (116) hide show
  1. package/README.md +76 -0
  2. package/dist/index.esm.js +10248 -0
  3. package/dist/index.esm.js.map +1 -0
  4. package/dist/index.js +7 -0
  5. package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
  6. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
  7. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
  8. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
  9. package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
  10. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
  11. package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
  12. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
  13. package/package.json +130 -0
  14. package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
  15. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
  16. package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
  17. package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
  18. package/src/ApolloInternetAccount/configSchema.ts +26 -0
  19. package/src/ApolloInternetAccount/index.ts +2 -0
  20. package/src/ApolloInternetAccount/model.ts +448 -0
  21. package/src/ApolloJobModel.ts +117 -0
  22. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
  23. package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
  24. package/src/ApolloSequenceAdapter/index.ts +21 -0
  25. package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
  26. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
  27. package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
  28. package/src/ApolloSixFrameRenderer/index.ts +3 -0
  29. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
  30. package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
  31. package/src/ApolloTextSearchAdapter/index.ts +18 -0
  32. package/src/BackendDrivers/BackendDriver.ts +31 -0
  33. package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
  34. package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
  35. package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
  36. package/src/BackendDrivers/index.ts +4 -0
  37. package/src/ChangeManager.ts +148 -0
  38. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
  39. package/src/LinearApolloDisplay/components/index.ts +1 -0
  40. package/src/LinearApolloDisplay/configSchema.ts +16 -0
  41. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
  42. package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
  43. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
  44. package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
  45. package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
  46. package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
  47. package/src/LinearApolloDisplay/index.ts +2 -0
  48. package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
  49. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
  50. package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
  51. package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
  52. package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
  53. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
  54. package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
  55. package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
  56. package/src/LinearApolloDisplay/types.ts +1 -0
  57. package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
  58. package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
  59. package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
  60. package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
  61. package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
  62. package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
  63. package/src/OntologyManager/OntologyStore/index.ts +526 -0
  64. package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
  65. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
  66. package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
  67. package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
  68. package/src/OntologyManager/index.ts +173 -0
  69. package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
  70. package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
  71. package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
  72. package/src/SixFrameFeatureDisplay/index.ts +2 -0
  73. package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
  74. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
  75. package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
  76. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
  77. package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
  78. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
  79. package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
  80. package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
  81. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
  82. package/src/TabularEditor/HybridGrid/index.ts +1 -0
  83. package/src/TabularEditor/TabularEditorPane.tsx +34 -0
  84. package/src/TabularEditor/index.ts +3 -0
  85. package/src/TabularEditor/model.ts +44 -0
  86. package/src/TabularEditor/types.ts +3 -0
  87. package/src/components/AddAssembly.tsx +464 -0
  88. package/src/components/AddChildFeature.tsx +247 -0
  89. package/src/components/AddFeature.tsx +252 -0
  90. package/src/components/CopyFeature.tsx +328 -0
  91. package/src/components/DeleteAssembly.tsx +185 -0
  92. package/src/components/DeleteFeature.tsx +90 -0
  93. package/src/components/Dialog.tsx +47 -0
  94. package/src/components/DownloadGFF3.tsx +213 -0
  95. package/src/components/ImportFeatures.tsx +295 -0
  96. package/src/components/ManageChecks.tsx +280 -0
  97. package/src/components/ManageUsers.tsx +218 -0
  98. package/src/components/ModifyFeatureAttribute.tsx +457 -0
  99. package/src/components/OntologyTermAutocomplete.tsx +240 -0
  100. package/src/components/OntologyTermMultiSelect.tsx +349 -0
  101. package/src/components/OpenLocalFile.tsx +178 -0
  102. package/src/components/ViewChangeLog.tsx +208 -0
  103. package/src/components/ViewCheckResults.tsx +151 -0
  104. package/src/components/index.ts +12 -0
  105. package/src/config.ts +10 -0
  106. package/src/declare.d.ts +3 -0
  107. package/src/extensions/annotationFromPileup.ts +208 -0
  108. package/src/extensions/index.ts +1 -0
  109. package/src/index.ts +394 -0
  110. package/src/makeDisplayComponent.tsx +244 -0
  111. package/src/session/ClientDataStore.ts +282 -0
  112. package/src/session/index.ts +1 -0
  113. package/src/session/session.ts +373 -0
  114. package/src/types.ts +10 -0
  115. package/src/util/index.ts +31 -0
  116. package/src/util/loadAssemblyIntoClient.ts +291 -0
@@ -0,0 +1,526 @@
1
+ import {
2
+ BlobLocation,
3
+ LocalPathLocation,
4
+ UriLocation,
5
+ isUriLocation,
6
+ } from '@jbrowse/core/util'
7
+ import { IDBPTransaction, IndexNames, StoreNames } from 'idb/with-async-ittr'
8
+
9
+ import { textSearch } from './fulltext'
10
+ import { OntologyDB, OntologyDBEdge, isDeprecated } from './indexeddb-schema'
11
+ import {
12
+ getTextIndexFields,
13
+ isDatabaseCurrent,
14
+ loadOboGraphJson,
15
+ openDatabase,
16
+ } from './indexeddb-storage'
17
+ import {
18
+ OntologyClass,
19
+ OntologyProperty,
20
+ OntologyTerm,
21
+ isOntologyClass,
22
+ isOntologyProperty,
23
+ } from '..'
24
+
25
+ export type SourceLocation = UriLocation | LocalPathLocation | BlobLocation
26
+
27
+ /** type alias for a Transaction on this particular DB schema */
28
+ export type Transaction<
29
+ TxStores extends ArrayLike<StoreNames<OntologyDB>> = ArrayLike<
30
+ StoreNames<OntologyDB>
31
+ >,
32
+ Mode extends IDBTransactionMode = 'readonly',
33
+ > = IDBPTransaction<OntologyDB, TxStores, Mode>
34
+
35
+ /** the format of the loading data source */
36
+ type SourceType = 'obo-graph-json' | 'obo' | 'owl'
37
+
38
+ /**
39
+ * @deprecated use the one from jbrowse core when it is published
40
+ **/
41
+ function isLocalPathLocation(location: unknown): location is LocalPathLocation {
42
+ return (
43
+ typeof location === 'object' && location !== null && 'localPath' in location
44
+ )
45
+ }
46
+
47
+ async function arrayFromAsync<T>(iter: AsyncIterable<T>) {
48
+ const a = []
49
+ for await (const i of iter) {
50
+ a.push(i)
51
+ }
52
+ return a
53
+ }
54
+
55
+ // /**
56
+ // * @deprecated use the one from jbrowse core when it is published
57
+ // */
58
+ // function isBlobLocation(location: unknown): location is BlobLocation {
59
+ // return (
60
+ // typeof location === 'object' && location !== null && 'blobId' in location
61
+ // )
62
+ // }
63
+
64
+ export interface OntologyStoreOptions {
65
+ prefixes?: Map<string, string>
66
+ textIndexing?: {
67
+ /** json paths of paths in the nodes to index as full text */
68
+ indexFields?: { displayName: string; jsonPath: string }[]
69
+ }
70
+ maxSearchResults?: number
71
+ }
72
+
73
+ export interface PropertiesOptions {
74
+ /** default true */
75
+ includeSubProperties: boolean
76
+ }
77
+
78
+ /** query interface for a specific ontology */
79
+ export default class OntologyStore {
80
+ ontologyName: string
81
+ ontologyVersion: string
82
+ sourceLocation: SourceLocation
83
+ db: ReturnType<OntologyStore['prepareDatabase']>
84
+ options: OntologyStoreOptions
85
+
86
+ loadOboGraphJson = loadOboGraphJson
87
+ getTermsByFulltext = textSearch
88
+ openDatabase = openDatabase
89
+ isDatabaseCurrent = isDatabaseCurrent
90
+
91
+ get textIndexFields() {
92
+ return getTextIndexFields.call(this)
93
+ }
94
+
95
+ get prefixes(): Map<string, string> {
96
+ return this.options.prefixes ?? new Map()
97
+ }
98
+
99
+ readonly DEFAULT_MAX_SEARCH_RESULTS = 100
100
+
101
+ constructor(
102
+ name: string,
103
+ version: string,
104
+ source: SourceLocation,
105
+ options?: OntologyStoreOptions,
106
+ ) {
107
+ this.ontologyName = name
108
+ this.ontologyVersion = version
109
+ this.sourceLocation = source
110
+ this.db = this.prepareDatabase()
111
+ this.options = options ?? {}
112
+ }
113
+
114
+ /**
115
+ * check that the configuration of this ontology appears valid. Does not
116
+ * try to do any fetches, however.
117
+ */
118
+ validate() {
119
+ const errors = []
120
+
121
+ // validate the source's file type
122
+ const { sourceLocation, sourceType } = this
123
+ if (!sourceType) {
124
+ errors.push(
125
+ new Error(
126
+ `unable to determine format of ontology source file ${JSON.stringify(
127
+ sourceLocation,
128
+ )}, file name must end with ".json", ".obo", or ".owl"`,
129
+ ),
130
+ )
131
+ } else if (sourceType !== 'obo-graph-json') {
132
+ errors.push(
133
+ new Error(
134
+ `ontology source file ${JSON.stringify(
135
+ sourceLocation,
136
+ )} has type ${sourceType}, which is not yet supported`,
137
+ ),
138
+ )
139
+ }
140
+
141
+ return errors
142
+ }
143
+
144
+ get sourceType(): SourceType | undefined {
145
+ if (isUriLocation(this.sourceLocation)) {
146
+ if (this.sourceLocation.uri.endsWith('.json')) {
147
+ return 'obo-graph-json'
148
+ }
149
+ } else if (
150
+ isLocalPathLocation(this.sourceLocation) &&
151
+ this.sourceLocation.localPath.endsWith('.json')
152
+ ) {
153
+ return 'obo-graph-json'
154
+ }
155
+ return undefined
156
+ }
157
+
158
+ /** base name of the IndexedDB database for this ontology */
159
+ get dbName() {
160
+ return `Apollo Ontology "${this.ontologyName}" "${this.ontologyVersion}"`
161
+ }
162
+
163
+ async prepareDatabase() {
164
+ const errors = this.validate()
165
+ if (errors.length > 0) {
166
+ throw errors
167
+ }
168
+
169
+ const db = await this.openDatabase(this.dbName)
170
+
171
+ // if database is already completely loaded, just return it
172
+ if (await this.isDatabaseCurrent(db)) {
173
+ return db
174
+ }
175
+
176
+ const { sourceLocation, sourceType } = this
177
+ if (sourceType === 'obo-graph-json') {
178
+ await this.loadOboGraphJson(db)
179
+ } else {
180
+ throw new Error(
181
+ `ontology source file ${JSON.stringify(
182
+ sourceLocation,
183
+ )} has type ${sourceType}, which is not yet supported`,
184
+ )
185
+ }
186
+
187
+ return db
188
+ }
189
+
190
+ async termCount(tx?: Transaction<['nodes']>) {
191
+ const db = await this.db
192
+ const myTx = tx ?? db.transaction('nodes')
193
+ return myTx.objectStore('nodes').count()
194
+ }
195
+
196
+ private async unique<ITEM extends { id: string }>(nodes: ITEM[]) {
197
+ const seen = new Map<string, boolean>()
198
+ const result: ITEM[] = []
199
+ for (const node of nodes) {
200
+ if (!seen.has(node.id)) {
201
+ seen.set(node.id, true)
202
+ result.push(node)
203
+ }
204
+ }
205
+ return result
206
+ }
207
+
208
+ async getTermsWithLabelOrSynonym(
209
+ termLabelOrSynonym: string,
210
+ options?: { includeSubclasses?: boolean },
211
+ tx?: Transaction<['nodes', 'edges']>,
212
+ ): Promise<OntologyTerm[]> {
213
+ const includeSubclasses = options?.includeSubclasses ?? true
214
+ const db = await this.db
215
+ const myTx = tx ?? db.transaction(['nodes', 'edges'])
216
+ const nodes = myTx.objectStore('nodes')
217
+ const resultNodes = [
218
+ ...(await nodes.index('by-label').getAll(termLabelOrSynonym)),
219
+ ...(await nodes.index('by-synonym').getAll(termLabelOrSynonym)),
220
+ ]
221
+
222
+ if (includeSubclasses) {
223
+ // now recursively traverse is_a relations to gather nodes that are subclasses any of these
224
+ const subclassIds = await this.recurseEdges(
225
+ 'by-object',
226
+ resultNodes.map((n) => n.id),
227
+ (edge) => edge.pred === 'is_a',
228
+ 'sub',
229
+ myTx as unknown as Transaction<['edges']>,
230
+ )
231
+ for (const nodeId of subclassIds) {
232
+ const node = await nodes.get(nodeId)
233
+ if (node) {
234
+ resultNodes.push(node)
235
+ }
236
+ }
237
+ }
238
+
239
+ return resultNodes
240
+ }
241
+
242
+ /**
243
+ * Get the ontology term for the property with the given label,
244
+ * plus all the terms for the properties that are "subPropertyOf"
245
+ * that property.
246
+ *
247
+ * If there is more than one property with that label, treats it as
248
+ * equivalent and just returns all the properties and their subproperties.
249
+ *
250
+ * options.includeSubProperties default is true
251
+ */
252
+ async getPropertiesByLabel(
253
+ propertyLabel: string,
254
+ options?: PropertiesOptions,
255
+ tx?: Transaction<['nodes', 'edges']>,
256
+ ): Promise<OntologyProperty[]> {
257
+ const includeSubProperties = options?.includeSubProperties ?? true
258
+
259
+ const db = await this.db
260
+ const myTx = tx ?? db.transaction(['nodes', 'edges'])
261
+
262
+ const terms = await this.getTermsWithLabelOrSynonym(
263
+ propertyLabel,
264
+ { includeSubclasses: false },
265
+ myTx,
266
+ )
267
+ const properties = terms.filter((p): p is OntologyProperty =>
268
+ isOntologyProperty(p),
269
+ )
270
+
271
+ if (includeSubProperties) {
272
+ const subPropertyIds = await this.recurseEdges(
273
+ 'by-object',
274
+ properties.map((p) => p.id),
275
+ (edge) => edge.pred === 'subPropertyOf',
276
+ 'sub',
277
+ myTx as unknown as Transaction<['edges']>,
278
+ )
279
+
280
+ const nodes = myTx.objectStore('nodes')
281
+ for (const subPropertyId of subPropertyIds) {
282
+ const property = await nodes.get(subPropertyId)
283
+ if (property && isOntologyProperty(property)) {
284
+ properties.push(property)
285
+ }
286
+ }
287
+ }
288
+
289
+ return properties
290
+ }
291
+
292
+ /** private helper for traversing the edges of the ontology graph. Does a breadth-first search of the graph */
293
+ private async recurseEdges(
294
+ queryIndex: IndexNames<OntologyDB, 'edges'>,
295
+ inputQueryIds: Iterable<string>,
296
+ filterEdge: (edge: OntologyDBEdge) => boolean,
297
+ resultProp: 'sub' | 'obj',
298
+ myTx: Transaction<['edges']>,
299
+ ) {
300
+ const resultIds = new Set<string>()
301
+
302
+ async function recur(queryIds: Iterable<string>) {
303
+ await Promise.all(
304
+ [...queryIds].map(async (queryId) => {
305
+ const theseResults = (
306
+ (await myTx
307
+ .objectStore('edges')
308
+ .index(queryIndex)
309
+ .getAll(queryId)) as OntologyDBEdge[]
310
+ )
311
+ .filter((element) => filterEdge(element))
312
+ .map((edge) => edge[resultProp])
313
+
314
+ if (theseResults.length > 0) {
315
+ // report these subjects as results
316
+ for (const resultId of theseResults) {
317
+ resultIds.add(resultId)
318
+ }
319
+
320
+ // and now recurse further through the edges
321
+ await recur(theseResults)
322
+ }
323
+ }),
324
+ )
325
+ }
326
+
327
+ await recur(inputQueryIds)
328
+ return resultIds.values()
329
+ }
330
+
331
+ /**
332
+ * given an array of node IDs, augment it with all of their subclasses or
333
+ * superclasses, and return the augmented array
334
+ **/
335
+ private async *expandNodeSet(
336
+ startingNodeIds: Iterable<string>,
337
+ subclassRelation = 'is_a',
338
+ direction: 'superclasses' | 'subclasses',
339
+ tx?: Transaction<['edges']>,
340
+ ) {
341
+ const db = await this.db
342
+ const myTx = tx ?? db.transaction(['edges'])
343
+ const startingNodes = [...startingNodeIds]
344
+ const subclassIds = await this.recurseEdges(
345
+ direction === 'subclasses' ? 'by-object' : 'by-subject',
346
+ startingNodes,
347
+ (edge) => edge.pred === subclassRelation,
348
+ direction === 'subclasses' ? 'sub' : 'obj',
349
+ myTx as unknown as Transaction<['edges']>,
350
+ )
351
+ for (const n of startingNodes) {
352
+ yield n
353
+ }
354
+ for (const id of subclassIds) {
355
+ yield id
356
+ }
357
+ }
358
+
359
+ /**
360
+ * given an iterator of node IDs, return a new iterator of those nodes plus all of their subclasses
361
+ */
362
+ expandSubclasses(
363
+ startingNodeIds: Iterable<string>,
364
+ subclassRelation = 'is_a',
365
+ tx?: Transaction<['edges']>,
366
+ ) {
367
+ return this.expandNodeSet(
368
+ startingNodeIds,
369
+ subclassRelation,
370
+ 'subclasses',
371
+ tx,
372
+ )
373
+ }
374
+
375
+ /**
376
+ * given an iterator of node IDs, return a new iterator of those nodes plus all of their superclasses
377
+ */
378
+ expandSuperclasses(
379
+ startingNodeIds: Iterable<string>,
380
+ subclassRelation = 'is_a',
381
+ tx?: Transaction<['edges']>,
382
+ ) {
383
+ return this.expandNodeSet(
384
+ startingNodeIds,
385
+ subclassRelation,
386
+ 'superclasses',
387
+ tx,
388
+ )
389
+ }
390
+
391
+ /**
392
+ * example: for the Sequence Ontology, store.getTermsThat('part_of', [geneTerm])
393
+ * would return all terms that are part_of, member_of, or integral_part_of a gene
394
+ */
395
+ async getClassesThat(
396
+ propertyLabel: string,
397
+ targetTerms: OntologyClass[],
398
+ tx?: Transaction<['nodes', 'edges']>,
399
+ ) {
400
+ const db = await this.db
401
+ const myTx = tx ?? db.transaction(['nodes', 'edges'])
402
+
403
+ // find all the terms for the properties we are using
404
+ const relatingProperties = await this.getPropertiesByLabel(
405
+ propertyLabel,
406
+ { includeSubProperties: true },
407
+ myTx,
408
+ )
409
+ const relatingPropertyIds = new Set(relatingProperties.map((p) => p.id))
410
+
411
+ // expand to search all the superclasses of the target terms
412
+ const targetTermsWithSuperClasses = await arrayFromAsync(
413
+ this.expandSuperclasses(
414
+ targetTerms.map((t) => t.id),
415
+ 'is_a',
416
+ myTx as unknown as Transaction<['edges']>,
417
+ ),
418
+ )
419
+
420
+ // these are all the terms that are related to the targets by the given properties
421
+ const termIds = await this.recurseEdges(
422
+ 'by-object',
423
+ targetTermsWithSuperClasses,
424
+ (edge) => relatingPropertyIds.has(edge.pred),
425
+ 'sub',
426
+ myTx as unknown as Transaction<['edges']>,
427
+ )
428
+
429
+ // expand to include all the subclasses of those terms
430
+ const expanded = this.expandSubclasses(
431
+ termIds,
432
+ 'is_a',
433
+ myTx as unknown as Transaction<['edges']>,
434
+ )
435
+
436
+ // fetch the full nodes and filter out deprecated ones
437
+ const terms: OntologyClass[] = []
438
+ for await (const termId of expanded) {
439
+ const node = await myTx.objectStore('nodes').get(termId)
440
+ if (node && isOntologyClass(node) && !isDeprecated(node)) {
441
+ terms.push(node)
442
+ }
443
+ }
444
+
445
+ return terms
446
+ }
447
+
448
+ async getClassesWithoutPropertyLabeled(
449
+ propertyLabel: string,
450
+ options: PropertiesOptions,
451
+ tx?: Transaction<['nodes', 'edges']>,
452
+ ) {
453
+ const db = await this.db
454
+ const myTx = tx ?? db.transaction(['nodes', 'edges'])
455
+ const nodeStore = myTx.objectStore('nodes')
456
+ const edgeStore = myTx.objectStore('edges')
457
+
458
+ // find all the terms (synonyms, subterms, etc) for the properties we are using
459
+ const relatingProperties = await this.getPropertiesByLabel(
460
+ propertyLabel,
461
+ options,
462
+ myTx,
463
+ )
464
+ const relatingPropertyIds = relatingProperties.map((p) => p.id)
465
+
466
+ // make a blacklist of all the term IDs that have those properties, plus their subclasses
467
+ const termIdsWithProperties = await (async () => {
468
+ const ids = new Set<string>()
469
+ for (const propertyId of relatingPropertyIds) {
470
+ for await (const cursor of edgeStore
471
+ .index('by-predicate')
472
+ .iterate(propertyId)) {
473
+ ids.add(cursor.value.sub)
474
+ }
475
+ }
476
+ // expand their subclasses
477
+ const expanded = new Set<string>()
478
+ for await (const id of this.expandSubclasses(
479
+ ids,
480
+ 'is_a',
481
+ myTx as unknown as Transaction<['edges']>,
482
+ )) {
483
+ expanded.add(id)
484
+ }
485
+ return expanded
486
+ })()
487
+
488
+ // iterate through all terms in the store, find ones that are CLASS
489
+ // and are not in the blacklist
490
+ const termIds: string[] = []
491
+ for await (const cursor of nodeStore) {
492
+ const node = cursor.value
493
+ if (isOntologyClass(node) && !termIdsWithProperties.has(node.id)) {
494
+ termIds.push(node.id)
495
+ }
496
+ }
497
+
498
+ // fetch the full nodes and filter out deprecated ones
499
+ const terms: OntologyClass[] = []
500
+ for await (const termId of termIds) {
501
+ const node = await myTx.objectStore('nodes').get(termId)
502
+ if (node && isOntologyClass(node) && !isDeprecated(node)) {
503
+ terms.push(node)
504
+ }
505
+ }
506
+
507
+ return terms
508
+ }
509
+
510
+ async getAllClasses(tx?: Transaction<['nodes']>): Promise<OntologyClass[]> {
511
+ const db = await this.db
512
+ const myTx = tx ?? db.transaction(['nodes'])
513
+ const all = (await myTx
514
+ .objectStore('nodes')
515
+ .index('by-type')
516
+ .getAll('CLASS')) as OntologyClass[]
517
+ return all.filter((term) => !isDeprecated(term))
518
+ }
519
+
520
+ async getAllTerms(tx?: Transaction<['nodes']>): Promise<OntologyTerm[]> {
521
+ const db = await this.db
522
+ const myTx = tx ?? db.transaction(['nodes'])
523
+ const all = await myTx.objectStore('nodes').getAll()
524
+ return all.filter((term) => !isDeprecated(term))
525
+ }
526
+ }
@@ -0,0 +1,89 @@
1
+ /** schema types used to strongly-type using the `idb` type system */
2
+ import { DBSchema } from 'idb/with-async-ittr'
3
+
4
+ import {
5
+ Edge as OboGraphEdge,
6
+ Meta as OboGraphMeta,
7
+ Node as OboGraphNode,
8
+ } from './obo-graph-json-schema'
9
+ import { OntologyStoreOptions, SourceLocation } from '.'
10
+
11
+ /** metadata about this IndexedDB ontology database */
12
+ export interface Meta {
13
+ /** original OntologyManager record this was loaded from */
14
+ ontologyRecord: {
15
+ name: string
16
+ version: string
17
+ sourceLocation: SourceLocation
18
+ }
19
+ storeOptions: OntologyStoreOptions
20
+ /** graph metadata in OBO Graph metadata format */
21
+ graphMeta?: OboGraphMeta
22
+ timestamp: string
23
+ /** IndexedDB schemaVersion for this ontology */
24
+ schemaVersion: number
25
+ /** time taken to load this data */
26
+ timings: {
27
+ /** milliseconds to fetch, parse, and load the ontology */
28
+ overall: number
29
+ /** optional milliseconds to fetch the ontology source file over the network */
30
+ fetch?: number
31
+ /** optional milliseconds to parse the ontology source file */
32
+ parse?: number
33
+ /** optional milliseconds to load the ontology into IndexedDB */
34
+ load?: number
35
+ }
36
+ }
37
+
38
+ // a Node that goes in the DB must have id and type, so make a new type that specifies this
39
+ export type OntologyDBNode = OboGraphNode & {
40
+ id: string
41
+ type: 'CLASS' | 'INDIVIDUAL' | 'PROPERTY'
42
+ fullTextWords?: string[]
43
+ }
44
+ export function isOntologyDBNode(node: OboGraphNode): node is OntologyDBNode {
45
+ return typeof node.id === 'string'
46
+ }
47
+
48
+ // an Edge that goes in our DB must have sub, pred, and obj, so make a new type and guard that specifies this
49
+ export type OntologyDBEdge = OboGraphEdge & {
50
+ sub: string
51
+ pred: string
52
+ obj: string
53
+ }
54
+ export function isOntologyDBEdge(edge: OboGraphEdge): edge is OntologyDBEdge {
55
+ return (
56
+ typeof edge.sub === 'string' &&
57
+ typeof edge.pred === 'string' &&
58
+ typeof edge.obj === 'string'
59
+ )
60
+ }
61
+
62
+ export function isDeprecated(thing: OntologyDBNode | OntologyDBEdge) {
63
+ return Boolean(thing.meta?.deprecated)
64
+ }
65
+
66
+ /** schema types used to strongly-type using the `idb` type system */
67
+ export interface OntologyDB extends DBSchema {
68
+ meta: { key: string; value: Meta }
69
+ nodes: {
70
+ key: string
71
+ value: OntologyDBNode
72
+ indexes: {
73
+ 'by-label': string
74
+ 'by-type': string
75
+ 'by-synonym': string
76
+ /** full-text index for fast searching by words */
77
+ 'full-text-words': string
78
+ }
79
+ }
80
+ edges: {
81
+ key: number
82
+ value: OntologyDBEdge
83
+ indexes: {
84
+ 'by-subject': string
85
+ 'by-object': string
86
+ 'by-predicate': string
87
+ }
88
+ }
89
+ }