cocoda-sdk 3.4.13 → 3.6.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 +83 -88
- package/dist/cjs/index.cjs +857 -66
- package/dist/cocoda-sdk.js +10 -10
- package/dist/cocoda-sdk.js.LICENSES.txt +3 -3
- package/dist/cocoda-sdk.js.map +4 -4
- package/dist/esm/providers/base-provider.js +30 -1
- package/dist/esm/providers/concept-api-provider.js +45 -34
- package/dist/esm/providers/contexts/context_jskos.js +240 -0
- package/dist/esm/providers/contexts/context_mod.js +59 -0
- package/dist/esm/providers/index.js +4 -0
- package/dist/esm/providers/mod-api-provider.js +452 -0
- package/dist/esm/providers/ols-api-provider.js +325 -0
- package/dist/esm/providers/skosmos-api-provider.js +0 -28
- package/package.json +13 -9
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import BaseProvider from "./base-provider.js";
|
|
2
|
+
class OlsApiProvider extends BaseProvider {
|
|
3
|
+
static providerName = "OlsApi";
|
|
4
|
+
static providerType = "http://bartoc.org/api-type/ols";
|
|
5
|
+
static supports = {
|
|
6
|
+
schemes: true,
|
|
7
|
+
top: true,
|
|
8
|
+
data: false,
|
|
9
|
+
// TODO
|
|
10
|
+
concepts: true,
|
|
11
|
+
narrower: true,
|
|
12
|
+
ancestors: true,
|
|
13
|
+
types: true,
|
|
14
|
+
suggest: true,
|
|
15
|
+
search: true
|
|
16
|
+
};
|
|
17
|
+
constructor(config) {
|
|
18
|
+
super(config);
|
|
19
|
+
this.endpoint = config.endpoint;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Used by `registryForScheme` (see src/lib/CocodaSDK.js) to determine a provider config for a concept schceme.
|
|
23
|
+
* TODO: make this obsolete
|
|
24
|
+
*/
|
|
25
|
+
static _registryConfigForBartocApiConfig({ url } = {}) {
|
|
26
|
+
if (url) {
|
|
27
|
+
return { endpoint: url };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Constructs the full API URL for a given endpoint.
|
|
32
|
+
*/
|
|
33
|
+
_getApiUrl(parts, params = {}) {
|
|
34
|
+
const url = this.endpoint + parts.join("/");
|
|
35
|
+
params = Object.fromEntries(Object.entries(params).filter(([_, v]) => v != null));
|
|
36
|
+
params = new URLSearchParams(params);
|
|
37
|
+
return params.size ? `${url}?${params}` : url;
|
|
38
|
+
}
|
|
39
|
+
_ontologyToJSKOS(ontology) {
|
|
40
|
+
const lan = ontology.lang || this._language || "en";
|
|
41
|
+
const scheme = {};
|
|
42
|
+
if (ontology.iri) {
|
|
43
|
+
scheme.uri = ontology.iri;
|
|
44
|
+
}
|
|
45
|
+
scheme.type = [
|
|
46
|
+
"http://www.w3.org/2004/02/skos/core#ConceptScheme"
|
|
47
|
+
];
|
|
48
|
+
if (ontology["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"]) {
|
|
49
|
+
scheme.type = scheme.type.concat(ontology["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"]);
|
|
50
|
+
}
|
|
51
|
+
if (ontology.title) {
|
|
52
|
+
scheme.prefLabel = {};
|
|
53
|
+
scheme.prefLabel[lan] = ontology.title;
|
|
54
|
+
}
|
|
55
|
+
if (ontology.description) {
|
|
56
|
+
scheme.definition = {};
|
|
57
|
+
scheme.definition[lan] = [ontology.description];
|
|
58
|
+
}
|
|
59
|
+
if (ontology.homepage) {
|
|
60
|
+
scheme.url = ontology.homepage;
|
|
61
|
+
}
|
|
62
|
+
if (ontology.tracker) {
|
|
63
|
+
scheme.issueTracker = [{ url: ontology.tracker }];
|
|
64
|
+
}
|
|
65
|
+
if (ontology.language) {
|
|
66
|
+
scheme.languages = ontology.language;
|
|
67
|
+
}
|
|
68
|
+
if (ontology.ontologyId) {
|
|
69
|
+
scheme.VOCID = ontology.ontologyId;
|
|
70
|
+
scheme.notation = [ontology.ontologyId];
|
|
71
|
+
}
|
|
72
|
+
if (ontology.license?.url) {
|
|
73
|
+
scheme.license = [{ uri: ontology.license.url }];
|
|
74
|
+
}
|
|
75
|
+
return scheme;
|
|
76
|
+
}
|
|
77
|
+
_termToJSKOS(term) {
|
|
78
|
+
const lan = term.language || this._language || "en";
|
|
79
|
+
const concept = {};
|
|
80
|
+
if (term.curie) {
|
|
81
|
+
concept.notation = [term.curie];
|
|
82
|
+
}
|
|
83
|
+
if (term.hasDirectChildren) {
|
|
84
|
+
concept.narrower = [null];
|
|
85
|
+
} else {
|
|
86
|
+
concept.narrower = [];
|
|
87
|
+
}
|
|
88
|
+
if (term.hasDirectParents) {
|
|
89
|
+
concept.broader = [null];
|
|
90
|
+
} else {
|
|
91
|
+
concept.broader = [];
|
|
92
|
+
}
|
|
93
|
+
if (term.iri) {
|
|
94
|
+
concept.uri = term.iri;
|
|
95
|
+
}
|
|
96
|
+
if (term["http://www.w3.org/2000/01/rdf-schema#label"]) {
|
|
97
|
+
concept.prefLabel = {};
|
|
98
|
+
concept.prefLabel[lan] = term["http://www.w3.org/2000/01/rdf-schema#label"];
|
|
99
|
+
}
|
|
100
|
+
concept.type = [
|
|
101
|
+
"http://www.w3.org/2004/02/skos/core#Concept",
|
|
102
|
+
// FIXME: properties are no classes
|
|
103
|
+
"http://www.w3.org/2002/07/owl#Class"
|
|
104
|
+
];
|
|
105
|
+
if (term.ontologyIri) {
|
|
106
|
+
concept.inScheme = [{ uri: term.ontologyIri }];
|
|
107
|
+
}
|
|
108
|
+
return concept;
|
|
109
|
+
}
|
|
110
|
+
// #### API REQUESTS ####
|
|
111
|
+
// TODO: rename _skipAdditionalParameters
|
|
112
|
+
async _request(url, config = { _skipAdditionalParameters: true }) {
|
|
113
|
+
if (url) {
|
|
114
|
+
url = new URL(url);
|
|
115
|
+
const given = Object.fromEntries(url.searchParams.entries());
|
|
116
|
+
return this.axios({
|
|
117
|
+
method: "get",
|
|
118
|
+
url: url.origin + url.pathname,
|
|
119
|
+
params: {
|
|
120
|
+
...given,
|
|
121
|
+
...config.params
|
|
122
|
+
// explicit params win
|
|
123
|
+
},
|
|
124
|
+
...config
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// API REQUESTS SCHEMES
|
|
129
|
+
async _paginate(base, query, limit) {
|
|
130
|
+
const size = limit > 0 ? limit : null;
|
|
131
|
+
let url = this._getApiUrl(base, { ...query, size });
|
|
132
|
+
let page = await this._request(url);
|
|
133
|
+
let items = page?.elements || [];
|
|
134
|
+
if (!size) {
|
|
135
|
+
const totalPages = page?.totalPages || 0;
|
|
136
|
+
for (let n = 1; n < totalPages; n++) {
|
|
137
|
+
url = this._getApiUrl(base, { ...query, page: n });
|
|
138
|
+
page = await this._request(url);
|
|
139
|
+
items = items.concat(page?.elements || []);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return items;
|
|
143
|
+
}
|
|
144
|
+
// API REQUESTS CONCEPTS
|
|
145
|
+
async _getConceptOls(concept) {
|
|
146
|
+
const VOCID = await this._getSchemeVOCID(concept?.inScheme?.[0]);
|
|
147
|
+
if (VOCID) {
|
|
148
|
+
let url = null;
|
|
149
|
+
if (concept.notation) {
|
|
150
|
+
url = this._getApiUrl(["ontologies", VOCID, "classes"], { curie: concept.notation });
|
|
151
|
+
} else if (concept.uri) {
|
|
152
|
+
url = this._getApiUrl(["ontologies", VOCID, "classes"], { iri: concept.uri });
|
|
153
|
+
}
|
|
154
|
+
let response = await this._request(url);
|
|
155
|
+
return response?.elements?.[0] || null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async _searchOls(search, scheme, limit, types) {
|
|
159
|
+
let items = [];
|
|
160
|
+
const knownTypes = {
|
|
161
|
+
"http://www.w3.org/2002/07/owl#Class": "classes",
|
|
162
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#Property": "properties"
|
|
163
|
+
};
|
|
164
|
+
if (!types?.length) {
|
|
165
|
+
types = Object.keys(knownTypes);
|
|
166
|
+
}
|
|
167
|
+
for (const type of types) {
|
|
168
|
+
if (type in knownTypes) {
|
|
169
|
+
const VOCID = scheme ? await this._getSchemeVOCID(scheme) : null;
|
|
170
|
+
const query = { search, ontology: VOCID };
|
|
171
|
+
if (!scheme || VOCID) {
|
|
172
|
+
const found = await this._paginate([knownTypes[type]], query, limit);
|
|
173
|
+
items.push(...found);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return items;
|
|
178
|
+
}
|
|
179
|
+
// UTILITIES
|
|
180
|
+
async _conceptIriFromObj(VOCID, conceptNotation) {
|
|
181
|
+
let url = this._getApiUrl(["ontologies", VOCID, "classes"], { curie: conceptNotation });
|
|
182
|
+
let response = await this._request(url);
|
|
183
|
+
if (response && response.elements && response.elements.length > 0) {
|
|
184
|
+
return response.elements[0].iri;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
get _language() {
|
|
188
|
+
return this._jskos.language || this.languages[0] || this._defaultLanguages[0] || "en";
|
|
189
|
+
}
|
|
190
|
+
async _getSchemeVOCID(scheme) {
|
|
191
|
+
return scheme?.VOCID ? scheme.VOCID : this._getScheme(scheme).then((s) => s?.ontologyId);
|
|
192
|
+
}
|
|
193
|
+
async _getScheme(scheme) {
|
|
194
|
+
if (scheme) {
|
|
195
|
+
const { VOCID, uri, notation, identifier } = scheme;
|
|
196
|
+
if (VOCID) {
|
|
197
|
+
return this._request(this._getApiUrl(["ontologies", VOCID]));
|
|
198
|
+
}
|
|
199
|
+
if (uri) {
|
|
200
|
+
scheme = await this._getSchemeFromUri(uri);
|
|
201
|
+
if (scheme) {
|
|
202
|
+
return scheme;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (notation?.[0] && (uri || identifier?.length)) {
|
|
206
|
+
scheme = await this._request(this._getApiUrl(["ontologies", notation[0]]));
|
|
207
|
+
if (scheme.iri === uri || identifier?.includes(scheme.iri)) {
|
|
208
|
+
return scheme;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (let id of identifier || []) {
|
|
212
|
+
const found = await this._getSchemeFromUri(id);
|
|
213
|
+
if (found) {
|
|
214
|
+
return found;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async _getSchemeFromUri(uri) {
|
|
220
|
+
if (uri) {
|
|
221
|
+
const url = this._getApiUrl(["ontologies"], { searchFields: "iri", search: uri });
|
|
222
|
+
const response = await this._request(url);
|
|
223
|
+
const schemes = response?.elements || [];
|
|
224
|
+
return schemes.reduce((short, cur) => cur.ontologyId.length < short.ontologyId.length ? cur : short, schemes[0]);
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
// MAIN FUNCTIONS
|
|
229
|
+
// TODO: query parameter are different: schemes are in "params.uri"?
|
|
230
|
+
async getSchemes({ schemes, limit }) {
|
|
231
|
+
let ontologies = [];
|
|
232
|
+
if (schemes) {
|
|
233
|
+
ontologies = (await Promise.all(schemes.map((s) => this._getScheme(s)))).filter(Boolean);
|
|
234
|
+
} else if (limit > 0) {
|
|
235
|
+
const url = this._getApiUrl(["ontologies"], { size: limit });
|
|
236
|
+
const response = await this._request(url);
|
|
237
|
+
ontologies = response.elements || [];
|
|
238
|
+
} else {
|
|
239
|
+
ontologies = await this._paginate(["ontologies"], {});
|
|
240
|
+
}
|
|
241
|
+
return Promise.all(ontologies.map((scheme) => this._ontologyToJSKOS(scheme)));
|
|
242
|
+
}
|
|
243
|
+
async getConcepts({ concepts, scheme, limit }) {
|
|
244
|
+
let result = [];
|
|
245
|
+
if (concepts) {
|
|
246
|
+
for (const concept of concepts) {
|
|
247
|
+
let item = await this._getConceptOls(concept);
|
|
248
|
+
if (item) {
|
|
249
|
+
result.push(await this._termToJSKOS(item));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} else if (scheme) {
|
|
253
|
+
const VOCID = await this._getSchemeVOCID(scheme);
|
|
254
|
+
if (VOCID) {
|
|
255
|
+
const items = await this._paginate(["ontologies", VOCID, "classes"], {}, limit);
|
|
256
|
+
result = Promise.all(items.map((item) => this._termToJSKOS(item)));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
async getTop({ scheme }) {
|
|
262
|
+
const VOCID = await this._getSchemeVOCID(scheme);
|
|
263
|
+
if (VOCID) {
|
|
264
|
+
let url = this._getApiUrl(["ontologies", VOCID, "classes"], { hasDirectParents: "false" });
|
|
265
|
+
let response = await this._request(url);
|
|
266
|
+
if (response?.elements) {
|
|
267
|
+
return Promise.all(response.elements.map((item) => this._termToJSKOS(item)));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
async _splitConcept(concept) {
|
|
273
|
+
const obj = {};
|
|
274
|
+
if (concept) {
|
|
275
|
+
obj.VOCID = await this._getSchemeVOCID(concept?.inScheme?.[0]);
|
|
276
|
+
obj.iri = concept.uri || await this._conceptIriFromObj(obj.VOCID, concept.notation);
|
|
277
|
+
obj.iri = encodeURIComponent(encodeURIComponent(obj.iri));
|
|
278
|
+
}
|
|
279
|
+
return obj;
|
|
280
|
+
}
|
|
281
|
+
async getNarrower({ concept }) {
|
|
282
|
+
const { VOCID, iri } = await this._splitConcept(concept);
|
|
283
|
+
if (VOCID && iri) {
|
|
284
|
+
let url = this._getApiUrl(["ontologies", VOCID, "classes", iri, "children"]);
|
|
285
|
+
let response = await this._request(url);
|
|
286
|
+
if (response?.elements) {
|
|
287
|
+
return Promise.all(response.elements.map((item) => this._termToJSKOS(item)));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
async getAncestors({ concept }) {
|
|
293
|
+
const { VOCID, iri } = await this._splitConcept(concept);
|
|
294
|
+
if (VOCID && iri) {
|
|
295
|
+
let url = this._getApiUrl(["ontologies", VOCID, "classes", iri, "ancestors"]);
|
|
296
|
+
let response = await this._request(url);
|
|
297
|
+
if (response?.elements) {
|
|
298
|
+
return Promise.all(response.elements.map((item) => this._termToJSKOS(item)));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
async getTypes() {
|
|
304
|
+
return [{
|
|
305
|
+
uri: "http://www.w3.org/2002/07/owl#Class",
|
|
306
|
+
prefLabel: {
|
|
307
|
+
en: "Class",
|
|
308
|
+
de: "Klasse"
|
|
309
|
+
}
|
|
310
|
+
}, {
|
|
311
|
+
uri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property",
|
|
312
|
+
prefLabel: {
|
|
313
|
+
en: "Property",
|
|
314
|
+
de: "Eigenschaft"
|
|
315
|
+
}
|
|
316
|
+
}];
|
|
317
|
+
}
|
|
318
|
+
async search({ search, scheme = null, limit = 0, types = ["http://www.w3.org/2002/07/owl#Class"] }) {
|
|
319
|
+
let items = await this._searchOls(search, scheme, limit, types);
|
|
320
|
+
return Promise.all(items.map((item) => this._termToJSKOS(item)));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
export {
|
|
324
|
+
OlsApiProvider as default
|
|
325
|
+
};
|
|
@@ -338,34 +338,6 @@ class SkosmosApiProvider extends BaseProvider {
|
|
|
338
338
|
const concepts = ancestors.map((c) => this._toJskosConcept(c, { scheme })).filter((c) => c.uri != concept.uri);
|
|
339
339
|
return concepts;
|
|
340
340
|
}
|
|
341
|
-
/**
|
|
342
|
-
* Returns suggestion result in OpenSearch Suggest Format.
|
|
343
|
-
*
|
|
344
|
-
* @param {Object} config
|
|
345
|
-
* @param {string} config.search search string
|
|
346
|
-
* @param {Object} [config.scheme] concept scheme to search in
|
|
347
|
-
* @param {number} [config.limit=100] maximum number of search results (default might be overridden by registry)
|
|
348
|
-
* @param {string[]} [config.types=[]] list of type URIs
|
|
349
|
-
* @returns {Array} result in OpenSearch Suggest Format
|
|
350
|
-
*/
|
|
351
|
-
async suggest(config) {
|
|
352
|
-
config._raw = true;
|
|
353
|
-
const concepts = await this.search(config);
|
|
354
|
-
const result = [config.search, [], [], []];
|
|
355
|
-
for (let concept of concepts) {
|
|
356
|
-
const notation = jskos.notation(concept);
|
|
357
|
-
const label = jskos.prefLabel(concept);
|
|
358
|
-
result[1].push((notation ? notation + " " : "") + label);
|
|
359
|
-
result[2].push("");
|
|
360
|
-
result[3].push(concept.uri);
|
|
361
|
-
}
|
|
362
|
-
if (concepts._totalCount != void 0) {
|
|
363
|
-
result._totalCount = concepts._totalCount;
|
|
364
|
-
} else {
|
|
365
|
-
result._totalCount = concepts.length;
|
|
366
|
-
}
|
|
367
|
-
return result;
|
|
368
|
-
}
|
|
369
341
|
/**
|
|
370
342
|
* Returns concept search results.
|
|
371
343
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cocoda-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "SDK for Cocoda",
|
|
5
5
|
"main": "dist/cjs/index.cjs",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "mocha --recursive",
|
|
21
|
+
"coverage": "c8 --reporter=text --skip-full mocha --exit",
|
|
22
|
+
"validate": "jskos-validate",
|
|
21
23
|
"lint": "eslint",
|
|
22
24
|
"fix": "eslint --fix",
|
|
23
25
|
"lint-staged": "lint-staged",
|
|
24
26
|
"build": "node build.js",
|
|
25
27
|
"docs": "jsdoc -c jsdoc.json",
|
|
26
28
|
"yesno": "node -e \"const yesno = require('yesno'); yesno({ question: 'Are you sure you want to continue?' }).then(ok => process.exit(ok ? 0 : 1));\"",
|
|
27
|
-
"release": "test $(git rev-parse --abbrev-ref HEAD) = dev && git pull && npm test && npm run build && npm version $SEMVER && npm run --silent yesno && (git push && git checkout
|
|
29
|
+
"release": "test $(git rev-parse --abbrev-ref HEAD) = dev && git pull && npm test && npm run build && npm version $SEMVER && npm run --silent yesno && (git push && git checkout main && git merge dev && git push --follow-tags && git checkout dev) || (git tag -d $(git describe --tags) && git reset --hard HEAD~1)",
|
|
28
30
|
"release:patch": "SEMVER=patch npm run release",
|
|
29
31
|
"release:minor": "SEMVER=minor npm run release",
|
|
30
32
|
"release:major": "SEMVER=major npm run release",
|
|
@@ -50,25 +52,27 @@
|
|
|
50
52
|
"homepage": "https://github.com/gbv/cocoda-sdk#readme",
|
|
51
53
|
"devDependencies": {
|
|
52
54
|
"axios-mock-adapter": "^2.1.0",
|
|
55
|
+
"c8": "^10.1.3",
|
|
53
56
|
"clean-jsdoc-theme": "^4.3.0",
|
|
54
|
-
"esbuild": "~0.
|
|
57
|
+
"esbuild": "~0.27.0",
|
|
55
58
|
"esbuild-plugin-ifdef": "^1.0.1",
|
|
56
|
-
"eslint": "~9.
|
|
57
|
-
"eslint-config-gbv": "~2.
|
|
58
|
-
"glob": "^
|
|
59
|
+
"eslint": "~9.39",
|
|
60
|
+
"eslint-config-gbv": "~2.7",
|
|
61
|
+
"glob": "^13.0.0",
|
|
59
62
|
"husky": "^9.1.7",
|
|
60
63
|
"jsdoc": "^4.0.4",
|
|
64
|
+
"jskos-cli": "^0.8.4",
|
|
61
65
|
"license-checker": "^25.0.1",
|
|
62
|
-
"lint-staged": "^
|
|
66
|
+
"lint-staged": "^16.2.7",
|
|
63
67
|
"mocha": "^11.0.1",
|
|
64
68
|
"yesno": "^0.4.0"
|
|
65
69
|
},
|
|
66
70
|
"dependencies": {
|
|
67
71
|
"axios": "^1.7.9",
|
|
68
|
-
"flexsearch": "~0.
|
|
72
|
+
"flexsearch": "~0.8.0",
|
|
69
73
|
"jskos-tools": "^1.0.43",
|
|
70
74
|
"localforage": "^1.10.0",
|
|
71
75
|
"lodash": "^4.17.21",
|
|
72
|
-
"uuid": "^
|
|
76
|
+
"uuid": "^13.0.0"
|
|
73
77
|
}
|
|
74
78
|
}
|