@jbrowse/plugin-rdf 2.6.1
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/LICENSE +201 -0
- package/dist/SPARQLAdapter/SPARQLAdapter.d.ts +23 -0
- package/dist/SPARQLAdapter/SPARQLAdapter.js +178 -0
- package/dist/SPARQLAdapter/SPARQLAdapter.js.map +1 -0
- package/dist/SPARQLAdapter/configSchema.d.ts +31 -0
- package/dist/SPARQLAdapter/configSchema.js +34 -0
- package/dist/SPARQLAdapter/configSchema.js.map +1 -0
- package/dist/SPARQLAdapter/index.d.ts +2 -0
- package/dist/SPARQLAdapter/index.js +11 -0
- package/dist/SPARQLAdapter/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/esm/SPARQLAdapter/SPARQLAdapter.d.ts +23 -0
- package/esm/SPARQLAdapter/SPARQLAdapter.js +172 -0
- package/esm/SPARQLAdapter/SPARQLAdapter.js.map +1 -0
- package/esm/SPARQLAdapter/configSchema.d.ts +31 -0
- package/esm/SPARQLAdapter/configSchema.js +32 -0
- package/esm/SPARQLAdapter/configSchema.js.map +1 -0
- package/esm/SPARQLAdapter/index.d.ts +2 -0
- package/esm/SPARQLAdapter/index.js +3 -0
- package/esm/SPARQLAdapter/index.js.map +1 -0
- package/esm/index.d.ts +6 -0
- package/esm/index.js +33 -0
- package/esm/index.js.map +1 -0
- package/package.json +56 -0
- package/src/SPARQLAdapter/README.md +115 -0
- package/src/SPARQLAdapter/SPARQLAdapter.test.ts +84 -0
- package/src/SPARQLAdapter/SPARQLAdapter.ts +257 -0
- package/src/SPARQLAdapter/__snapshots__/SPARQLAdapter.test.ts.snap +449 -0
- package/src/SPARQLAdapter/configSchema.ts +40 -0
- package/src/SPARQLAdapter/index.ts +2 -0
- package/src/SPARQLAdapter/test_data/emptyQueryResponse.json +30 -0
- package/src/SPARQLAdapter/test_data/queryResponse.json +3834 -0
- package/src/SPARQLAdapter/test_data/refNamesResponse.json +114 -0
- package/src/__snapshots__/index.test.ts.snap +3 -0
- package/src/index.test.ts +18 -0
- package/src/index.ts +46 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseFeatureDataAdapter,
|
|
3
|
+
BaseOptions,
|
|
4
|
+
} from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
5
|
+
import { NoAssemblyRegion } from '@jbrowse/core/util/types'
|
|
6
|
+
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
|
|
7
|
+
import SimpleFeature, { Feature } from '@jbrowse/core/util/simpleFeature'
|
|
8
|
+
import format from 'string-template'
|
|
9
|
+
|
|
10
|
+
import { Instance } from 'mobx-state-tree'
|
|
11
|
+
import { readConfObject } from '@jbrowse/core/configuration'
|
|
12
|
+
import MyConfigSchema from './configSchema'
|
|
13
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
14
|
+
import { getSubAdapterType } from '@jbrowse/core/data_adapters/dataAdapterCache'
|
|
15
|
+
|
|
16
|
+
interface SPARQLEntry {
|
|
17
|
+
type: string
|
|
18
|
+
value: string
|
|
19
|
+
dataTypes?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SPARQLBinding {
|
|
23
|
+
[key: string]: SPARQLEntry
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SPARQLResponseHead {
|
|
27
|
+
vars: string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SPARQLResponseResults {
|
|
31
|
+
bindings: SPARQLBinding[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface SPARQLResponse {
|
|
35
|
+
head: SPARQLResponseHead
|
|
36
|
+
results: SPARQLResponseResults
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SPARQLFeatureData {
|
|
40
|
+
start: number
|
|
41
|
+
end: number
|
|
42
|
+
strand: number
|
|
43
|
+
subfeatures?: SPARQLFeatureData[]
|
|
44
|
+
uniqueId: string
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
[propName: string]: any
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface SPARQLFeature {
|
|
50
|
+
data: SPARQLFeatureData
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default class SPARQLAdapter extends BaseFeatureDataAdapter {
|
|
54
|
+
private endpoint: string
|
|
55
|
+
|
|
56
|
+
private queryTemplate: string
|
|
57
|
+
|
|
58
|
+
private refNamesQueryTemplate: string
|
|
59
|
+
|
|
60
|
+
private additionalQueryParams: string[]
|
|
61
|
+
|
|
62
|
+
private configRefNames: string[]
|
|
63
|
+
|
|
64
|
+
private refNames: string[] | undefined
|
|
65
|
+
|
|
66
|
+
public constructor(
|
|
67
|
+
config: Instance<typeof MyConfigSchema>,
|
|
68
|
+
getSubAdapter?: getSubAdapterType,
|
|
69
|
+
pluginManager?: PluginManager,
|
|
70
|
+
) {
|
|
71
|
+
super(config, getSubAdapter, pluginManager)
|
|
72
|
+
this.endpoint = readConfObject(config, 'endpoint').uri
|
|
73
|
+
this.queryTemplate = readConfObject(config, 'queryTemplate')
|
|
74
|
+
this.additionalQueryParams = readConfObject(config, 'additionalQueryParams')
|
|
75
|
+
this.refNamesQueryTemplate = readConfObject(config, 'refNamesQueryTemplate')
|
|
76
|
+
this.configRefNames = readConfObject(config, 'refNames')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public async getRefNames(opts: BaseOptions = {}): Promise<string[]> {
|
|
80
|
+
if (this.refNames) {
|
|
81
|
+
return this.refNames
|
|
82
|
+
}
|
|
83
|
+
let refNames = [] as string[]
|
|
84
|
+
if (this.refNamesQueryTemplate) {
|
|
85
|
+
const queryTemplate = encodeURIComponent(this.refNamesQueryTemplate)
|
|
86
|
+
const results = await this.querySparql(queryTemplate, opts)
|
|
87
|
+
refNames = this.resultsToRefNames(results)
|
|
88
|
+
} else if (this.configRefNames) {
|
|
89
|
+
refNames = this.configRefNames
|
|
90
|
+
}
|
|
91
|
+
this.refNames = refNames
|
|
92
|
+
return refNames
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public getFeatures(query: NoAssemblyRegion, opts: BaseOptions = {}) {
|
|
96
|
+
return ObservableCreate<Feature>(async observer => {
|
|
97
|
+
const filledTemplate = encodeURIComponent(
|
|
98
|
+
format(this.queryTemplate, query),
|
|
99
|
+
)
|
|
100
|
+
const { refName } = query
|
|
101
|
+
const results = await this.querySparql(filledTemplate, opts)
|
|
102
|
+
this.resultsToFeatures(results, refName).forEach(feature => {
|
|
103
|
+
observer.next(feature)
|
|
104
|
+
})
|
|
105
|
+
observer.complete()
|
|
106
|
+
}, opts.signal)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
|
+
private async querySparql(query: string, opts?: BaseOptions): Promise<any> {
|
|
111
|
+
let additionalQueryParams = ''
|
|
112
|
+
if (this.additionalQueryParams.length) {
|
|
113
|
+
additionalQueryParams = `&${this.additionalQueryParams.join('&')}`
|
|
114
|
+
}
|
|
115
|
+
const signal = opts && opts.signal
|
|
116
|
+
const response = await fetch(
|
|
117
|
+
`${this.endpoint}?query=${query}${additionalQueryParams}`,
|
|
118
|
+
{
|
|
119
|
+
headers: { accept: 'application/json,application/sparql-results+json' },
|
|
120
|
+
signal,
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
return response.json()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private resultsToRefNames(response: SPARQLResponse): string[] {
|
|
127
|
+
const rows = ((response || {}).results || {}).bindings || []
|
|
128
|
+
if (!rows.length) {
|
|
129
|
+
return []
|
|
130
|
+
}
|
|
131
|
+
const fields = response.head.vars
|
|
132
|
+
if (!fields.includes('refName')) {
|
|
133
|
+
throw new Error('"refName" not found in refNamesQueryTemplate response')
|
|
134
|
+
}
|
|
135
|
+
return rows.map(row => row.refName.value)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private resultsToFeatures(
|
|
139
|
+
results: SPARQLResponse,
|
|
140
|
+
refName: string,
|
|
141
|
+
): SimpleFeature[] {
|
|
142
|
+
const rows = ((results || {}).results || {}).bindings || []
|
|
143
|
+
if (!rows.length) {
|
|
144
|
+
return []
|
|
145
|
+
}
|
|
146
|
+
const fields = results.head.vars
|
|
147
|
+
const requiredFields = ['start', 'end', 'uniqueId']
|
|
148
|
+
requiredFields.forEach(requiredField => {
|
|
149
|
+
if (!fields.includes(requiredField)) {
|
|
150
|
+
console.error(
|
|
151
|
+
`Required field ${requiredField} missing from feature data`,
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
const seenFeatures: Record<string, SPARQLFeature> = {}
|
|
156
|
+
rows.forEach(row => {
|
|
157
|
+
const rawData: Record<string, string>[] = [{}]
|
|
158
|
+
fields.forEach(field => {
|
|
159
|
+
if (field in row) {
|
|
160
|
+
const { value } = row[field]
|
|
161
|
+
let idx = 0
|
|
162
|
+
while (field.startsWith('sub_')) {
|
|
163
|
+
field = field.slice(4)
|
|
164
|
+
idx += 1
|
|
165
|
+
}
|
|
166
|
+
while (idx > rawData.length - 1) {
|
|
167
|
+
rawData.push({})
|
|
168
|
+
}
|
|
169
|
+
rawData[idx][field] = value
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
rawData.forEach((rd, idx) => {
|
|
174
|
+
const { uniqueId } = rd
|
|
175
|
+
if (idx < rawData.length - 1) {
|
|
176
|
+
rawData[idx + 1].parentUniqueId = uniqueId
|
|
177
|
+
}
|
|
178
|
+
seenFeatures[uniqueId] = {
|
|
179
|
+
data: {
|
|
180
|
+
...rd,
|
|
181
|
+
uniqueId,
|
|
182
|
+
refName,
|
|
183
|
+
start: parseInt(rd.start, 10),
|
|
184
|
+
end: parseInt(rd.end, 10),
|
|
185
|
+
strand: parseInt(rd.strand, 10) || 0,
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// resolve subfeatures, keeping only top-level features in seenFeatures
|
|
192
|
+
for (const [uniqueId, f] of Object.entries(seenFeatures)) {
|
|
193
|
+
const pid = f.data.parentUniqueId
|
|
194
|
+
delete f.data.parentUniqueId
|
|
195
|
+
if (pid) {
|
|
196
|
+
const p = seenFeatures[pid]
|
|
197
|
+
if (p) {
|
|
198
|
+
if (!p.data.subfeatures) {
|
|
199
|
+
p.data.subfeatures = []
|
|
200
|
+
}
|
|
201
|
+
p.data.subfeatures.push({
|
|
202
|
+
...f.data,
|
|
203
|
+
uniqueId,
|
|
204
|
+
})
|
|
205
|
+
delete seenFeatures[uniqueId]
|
|
206
|
+
} else {
|
|
207
|
+
const subfeatures = Object.values(seenFeatures)
|
|
208
|
+
.map(sf => sf.data.subfeatures)
|
|
209
|
+
.filter(sf => !!sf)
|
|
210
|
+
.flat()
|
|
211
|
+
let found = false
|
|
212
|
+
for (const subfeature of subfeatures) {
|
|
213
|
+
if (subfeature && subfeature.uniqueId === pid) {
|
|
214
|
+
if (!subfeature.subfeatures) {
|
|
215
|
+
subfeature.subfeatures = []
|
|
216
|
+
}
|
|
217
|
+
subfeature.subfeatures.push({
|
|
218
|
+
...f.data,
|
|
219
|
+
uniqueId,
|
|
220
|
+
})
|
|
221
|
+
delete seenFeatures[uniqueId]
|
|
222
|
+
found = true
|
|
223
|
+
break
|
|
224
|
+
} else if (subfeature && subfeature.subfeatures) {
|
|
225
|
+
subfeatures.push(...subfeature.subfeatures)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (!found) {
|
|
229
|
+
console.error(`Could not find parentID ${pid}`)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return Object.keys(seenFeatures).map(
|
|
236
|
+
seenFeature =>
|
|
237
|
+
new SimpleFeature({
|
|
238
|
+
...seenFeatures[seenFeature].data,
|
|
239
|
+
uniqueId: seenFeature,
|
|
240
|
+
subfeatures: seenFeatures[seenFeature].data.subfeatures,
|
|
241
|
+
}),
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public async hasDataForRefName(
|
|
246
|
+
refName: string,
|
|
247
|
+
opts: BaseOptions = {},
|
|
248
|
+
): Promise<boolean> {
|
|
249
|
+
const refNames = await this.getRefNames(opts)
|
|
250
|
+
if (refNames.length && !refNames.includes(refName)) {
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
return true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public freeResources(/* { region } */): void {}
|
|
257
|
+
}
|