@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.
Files changed (38) hide show
  1. package/LICENSE +201 -0
  2. package/dist/SPARQLAdapter/SPARQLAdapter.d.ts +23 -0
  3. package/dist/SPARQLAdapter/SPARQLAdapter.js +178 -0
  4. package/dist/SPARQLAdapter/SPARQLAdapter.js.map +1 -0
  5. package/dist/SPARQLAdapter/configSchema.d.ts +31 -0
  6. package/dist/SPARQLAdapter/configSchema.js +34 -0
  7. package/dist/SPARQLAdapter/configSchema.js.map +1 -0
  8. package/dist/SPARQLAdapter/index.d.ts +2 -0
  9. package/dist/SPARQLAdapter/index.js +11 -0
  10. package/dist/SPARQLAdapter/index.js.map +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.js +39 -0
  13. package/dist/index.js.map +1 -0
  14. package/esm/SPARQLAdapter/SPARQLAdapter.d.ts +23 -0
  15. package/esm/SPARQLAdapter/SPARQLAdapter.js +172 -0
  16. package/esm/SPARQLAdapter/SPARQLAdapter.js.map +1 -0
  17. package/esm/SPARQLAdapter/configSchema.d.ts +31 -0
  18. package/esm/SPARQLAdapter/configSchema.js +32 -0
  19. package/esm/SPARQLAdapter/configSchema.js.map +1 -0
  20. package/esm/SPARQLAdapter/index.d.ts +2 -0
  21. package/esm/SPARQLAdapter/index.js +3 -0
  22. package/esm/SPARQLAdapter/index.js.map +1 -0
  23. package/esm/index.d.ts +6 -0
  24. package/esm/index.js +33 -0
  25. package/esm/index.js.map +1 -0
  26. package/package.json +56 -0
  27. package/src/SPARQLAdapter/README.md +115 -0
  28. package/src/SPARQLAdapter/SPARQLAdapter.test.ts +84 -0
  29. package/src/SPARQLAdapter/SPARQLAdapter.ts +257 -0
  30. package/src/SPARQLAdapter/__snapshots__/SPARQLAdapter.test.ts.snap +449 -0
  31. package/src/SPARQLAdapter/configSchema.ts +40 -0
  32. package/src/SPARQLAdapter/index.ts +2 -0
  33. package/src/SPARQLAdapter/test_data/emptyQueryResponse.json +30 -0
  34. package/src/SPARQLAdapter/test_data/queryResponse.json +3834 -0
  35. package/src/SPARQLAdapter/test_data/refNamesResponse.json +114 -0
  36. package/src/__snapshots__/index.test.ts.snap +3 -0
  37. package/src/index.test.ts +18 -0
  38. 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
+ }