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.
@@ -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.4.13",
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 master && git merge dev && git push --follow-tags && git checkout dev) || (git tag -d $(git describe --tags) && git reset --hard HEAD~1)",
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.24.0",
57
+ "esbuild": "~0.27.0",
55
58
  "esbuild-plugin-ifdef": "^1.0.1",
56
- "eslint": "~9.16",
57
- "eslint-config-gbv": "~2.6",
58
- "glob": "^10.4.5",
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": "^15.2.11",
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.6.32",
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": "^11.0.3"
76
+ "uuid": "^13.0.0"
73
77
  }
74
78
  }