@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.
- package/README.md +76 -0
- package/dist/index.esm.js +10248 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +7 -0
- package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
- package/package.json +130 -0
- package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
- package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
- package/src/ApolloInternetAccount/configSchema.ts +26 -0
- package/src/ApolloInternetAccount/index.ts +2 -0
- package/src/ApolloInternetAccount/model.ts +448 -0
- package/src/ApolloJobModel.ts +117 -0
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
- package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
- package/src/ApolloSequenceAdapter/index.ts +21 -0
- package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
- package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
- package/src/ApolloSixFrameRenderer/index.ts +3 -0
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
- package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
- package/src/ApolloTextSearchAdapter/index.ts +18 -0
- package/src/BackendDrivers/BackendDriver.ts +31 -0
- package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
- package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
- package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
- package/src/BackendDrivers/index.ts +4 -0
- package/src/ChangeManager.ts +148 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
- package/src/LinearApolloDisplay/components/index.ts +1 -0
- package/src/LinearApolloDisplay/configSchema.ts +16 -0
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
- package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
- package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
- package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
- package/src/LinearApolloDisplay/index.ts +2 -0
- package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
- package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
- package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
- package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
- package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
- package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
- package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
- package/src/LinearApolloDisplay/types.ts +1 -0
- package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
- package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
- package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
- package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
- package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
- package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
- package/src/OntologyManager/OntologyStore/index.ts +526 -0
- package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
- package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
- package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
- package/src/OntologyManager/index.ts +173 -0
- package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
- package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
- package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
- package/src/SixFrameFeatureDisplay/index.ts +2 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
- package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
- package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
- package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
- package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
- package/src/TabularEditor/HybridGrid/index.ts +1 -0
- package/src/TabularEditor/TabularEditorPane.tsx +34 -0
- package/src/TabularEditor/index.ts +3 -0
- package/src/TabularEditor/model.ts +44 -0
- package/src/TabularEditor/types.ts +3 -0
- package/src/components/AddAssembly.tsx +464 -0
- package/src/components/AddChildFeature.tsx +247 -0
- package/src/components/AddFeature.tsx +252 -0
- package/src/components/CopyFeature.tsx +328 -0
- package/src/components/DeleteAssembly.tsx +185 -0
- package/src/components/DeleteFeature.tsx +90 -0
- package/src/components/Dialog.tsx +47 -0
- package/src/components/DownloadGFF3.tsx +213 -0
- package/src/components/ImportFeatures.tsx +295 -0
- package/src/components/ManageChecks.tsx +280 -0
- package/src/components/ManageUsers.tsx +218 -0
- package/src/components/ModifyFeatureAttribute.tsx +457 -0
- package/src/components/OntologyTermAutocomplete.tsx +240 -0
- package/src/components/OntologyTermMultiSelect.tsx +349 -0
- package/src/components/OpenLocalFile.tsx +178 -0
- package/src/components/ViewChangeLog.tsx +208 -0
- package/src/components/ViewCheckResults.tsx +151 -0
- package/src/components/index.ts +12 -0
- package/src/config.ts +10 -0
- package/src/declare.d.ts +3 -0
- package/src/extensions/annotationFromPileup.ts +208 -0
- package/src/extensions/index.ts +1 -0
- package/src/index.ts +394 -0
- package/src/makeDisplayComponent.tsx +244 -0
- package/src/session/ClientDataStore.ts +282 -0
- package/src/session/index.ts +1 -0
- package/src/session/session.ts +373 -0
- package/src/types.ts +10 -0
- package/src/util/index.ts +31 -0
- 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
|
+
}
|