cocoda-sdk 3.1.0 → 3.2.2

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.
@@ -41,10 +41,12 @@ class BaseProvider {
41
41
  this._config = {};
42
42
  this.setRetryConfig();
43
43
  this.axios.interceptors.request.use((config) => {
44
- const language = _.uniq([].concat(_.get(config, "params.language", "").split(","), this.languages, this._defaultLanguages).filter((lang) => lang != "")).join(",");
45
- _.set(config, "params.language", language);
46
- if (this.has.auth && this._auth.bearerToken && !_.get(config, "headers.Authorization")) {
47
- _.set(config, "headers.Authorization", `Bearer ${this._auth.bearerToken}`);
44
+ if (!config._skipAdditionalParameters) {
45
+ const language = _.uniq([].concat(_.get(config, "params.language", "").split(","), this.languages, this._defaultLanguages).filter((lang) => lang != "")).join(",");
46
+ _.set(config, "params.language", language);
47
+ if (this.has.auth && this._auth.bearerToken && !_.get(config, "headers.Authorization")) {
48
+ _.set(config, "headers.Authorization", `Bearer ${this._auth.bearerToken}`);
49
+ }
48
50
  }
49
51
  if (config.url.startsWith("http:") && typeof window !== "undefined" && window.location.protocol == "https:") {
50
52
  throw new axios.Cancel("Can't call http API from https.");
@@ -7,6 +7,7 @@ import ReconciliationApiProvider from "./reconciliation-api-provider.js";
7
7
  import LabelSearchSuggestionProvider from "./label-search-suggestion-provider.js";
8
8
  import SkosmosApiProvider from "./skosmos-api-provider.js";
9
9
  import LocApiProvider from "./loc-api-provider.js";
10
+ import SkohubProvider from "./skohub-provider.js";
10
11
  export {
11
12
  BaseProvider,
12
13
  ConceptApiProvider,
@@ -16,5 +17,6 @@ export {
16
17
  MappingsApiProvider,
17
18
  OccurrencesApiProvider,
18
19
  ReconciliationApiProvider,
20
+ SkohubProvider,
19
21
  SkosmosApiProvider
20
22
  };
@@ -78,7 +78,7 @@ class MappingsApiProvider extends BaseProvider {
78
78
  throw error;
79
79
  }
80
80
  }
81
- async getMappings({ from, fromScheme, to, toScheme, creator, type, partOf, offset, limit, direction, mode, identifier, cardinality, sort, order, ...config }) {
81
+ async getMappings({ from, fromScheme, to, toScheme, creator, type, partOf, offset, limit, direction, mode, identifier, cardinality, annotatedBy, annotatedFor, annotatedWith, sort, order, ...config }) {
82
82
  let params = {}, url = this._api.mappings;
83
83
  if (from) {
84
84
  params.from = _.isString(from) ? from : from.uri;
@@ -113,6 +113,15 @@ class MappingsApiProvider extends BaseProvider {
113
113
  if (cardinality) {
114
114
  params.cardinality = cardinality;
115
115
  }
116
+ if (annotatedBy) {
117
+ params.annotatedBy = annotatedBy;
118
+ }
119
+ if (annotatedFor) {
120
+ params.annotatedFor = annotatedFor;
121
+ }
122
+ if (annotatedWith) {
123
+ params.annotatedWith = annotatedWith;
124
+ }
116
125
  if (mode) {
117
126
  params.mode = mode;
118
127
  }
@@ -36,8 +36,10 @@ class OccurrencesApiProvider extends BaseProvider {
36
36
  }
37
37
  async getMappings(config) {
38
38
  const occurrences = await this.getOccurrences(config);
39
- const fromScheme = _.get(config, "from.inScheme[0]") || config.fromScheme;
40
- const toScheme = _.get(config, "to.inScheme[0]") || config.toScheme;
39
+ const from = config.from;
40
+ const fromScheme = _.get(from, "inScheme[0]") || config.fromScheme;
41
+ const to = config.to;
42
+ const toScheme = _.get(to, "inScheme[0]") || config.toScheme;
41
43
  const mappings = [];
42
44
  for (let occurrence of occurrences) {
43
45
  if (!occurrence) {
@@ -58,7 +60,7 @@ class OccurrencesApiProvider extends BaseProvider {
58
60
  mapping.to = { memberSet: [] };
59
61
  }
60
62
  mapping.toScheme = _.get(occurrence, "memberSet[1].inScheme[0]");
61
- if (fromScheme && mapping.fromScheme && !jskos.compare(mapping.fromScheme, fromScheme) || toScheme && mapping.toScheme && !jskos.compare(mapping.toScheme, toScheme)) {
63
+ if (from && jskos.compare(from, _.get(mapping, "to.memberSet[0]")) || to && jskos.compare(to, _.get(mapping, "from.memberSet[0]"))) {
62
64
  [mapping.from, mapping.fromScheme, mapping.to, mapping.toScheme] = [mapping.to, mapping.toScheme, mapping.from, mapping.fromScheme];
63
65
  }
64
66
  if (!mapping.fromScheme && fromScheme) {
@@ -70,14 +72,12 @@ class OccurrencesApiProvider extends BaseProvider {
70
72
  mapping.type = [jskos.defaultMappingType.uri];
71
73
  mapping._occurrence = occurrence;
72
74
  mapping = jskos.addMappingIdentifiers(mapping);
73
- if (occurrence.database) {
74
- mapping.creator = [occurrence.database];
75
- }
76
75
  mappings.push(mapping);
77
76
  }
77
+ mappings._url = occurrences._url;
78
78
  return mappings;
79
79
  }
80
- async getOccurrences({ from, to, concepts, ...config }) {
80
+ async getOccurrences({ from, to, concepts, threshold = 0, ...config }) {
81
81
  let promises = [];
82
82
  concepts = (concepts || []).concat([from, to]).filter((c) => !!c);
83
83
  for (let concept of concepts) {
@@ -101,17 +101,7 @@ class OccurrencesApiProvider extends BaseProvider {
101
101
  params: {
102
102
  member: uri,
103
103
  scheme: "*",
104
- threshold: 5
105
- }
106
- }));
107
- }
108
- if (uris.length > 1) {
109
- let urisString = uris.join(" ");
110
- promises.push(this._getOccurrences({
111
- ...config,
112
- params: {
113
- member: urisString,
114
- threshold: 5
104
+ threshold
115
105
  }
116
106
  }));
117
107
  }
@@ -135,7 +125,9 @@ class OccurrencesApiProvider extends BaseProvider {
135
125
  delete occurrences[value];
136
126
  });
137
127
  occurrences = occurrences.filter((o) => o != null);
138
- return occurrences.sort((a, b) => parseInt(b.count || 0) - parseInt(a.count || 0));
128
+ occurrences = occurrences.sort((a, b) => parseInt(b.count || 0) - parseInt(a.count || 0));
129
+ occurrences._url = results.map((result) => result._url);
130
+ return occurrences;
139
131
  }
140
132
  async _getOccurrences(config) {
141
133
  let resultsFromCache = this._cache.find((item) => {
@@ -0,0 +1,228 @@
1
+ import BaseProvider from "./base-provider.js";
2
+ import * as _ from "../utils/lodash.js";
3
+ import * as errors from "../errors/index.js";
4
+ import { listOfCapabilities } from "../utils/index.js";
5
+ import jskos from "jskos-tools";
6
+ import FlexSearch from "flexsearch";
7
+ function decodeUnicode(text) {
8
+ return text.replace(/\\u[\dA-F]{4}/gi, function(match) {
9
+ return String.fromCharCode(parseInt(match.replace(/\\u/g, ""), 16));
10
+ });
11
+ }
12
+ class SkohubProvider extends BaseProvider {
13
+ _prepare() {
14
+ this.has.schemes = true;
15
+ this.has.top = true;
16
+ this.has.data = true;
17
+ this.has.concepts = true;
18
+ this.has.narrower = true;
19
+ this.has.ancestors = true;
20
+ this.has.suggest = true;
21
+ this.has.search = true;
22
+ listOfCapabilities.filter((c) => !this.has[c]).forEach((c) => {
23
+ this.has[c] = false;
24
+ });
25
+ }
26
+ _setup() {
27
+ this._index = {};
28
+ this._conceptCache = {};
29
+ this._schemeCache = {};
30
+ }
31
+ static _registryConfigForBartocApiConfig({ url, scheme } = {}) {
32
+ if (!url || !scheme) {
33
+ return null;
34
+ }
35
+ const newScheme = { uri: url, identifier: jskos.getAllUris(scheme).filter((uri) => uri !== url) };
36
+ return { schemes: [newScheme] };
37
+ }
38
+ async _loadScheme({ scheme, ...config }) {
39
+ let uris = jskos.getAllUris(scheme);
40
+ for (let uri2 of uris) {
41
+ if (this._schemeCache[uri2]) {
42
+ return this._schemeCache[uri2];
43
+ }
44
+ }
45
+ const schemeFromList = this.schemes.find((s) => jskos.compare(s, scheme));
46
+ if (!schemeFromList || !schemeFromList.uri) {
47
+ throw new errors.InvalidRequestError({ message: `Tried to load unsupported scheme (${scheme && scheme.uri})` });
48
+ }
49
+ const uri = schemeFromList.uri;
50
+ uris = _.uniq(uris.concat(jskos.getAllUris(schemeFromList)));
51
+ let postfix = ".json";
52
+ if (uri.endsWith("/")) {
53
+ postfix = "index.json";
54
+ }
55
+ const data = await this.axios({ ...config, url: `${uri}${postfix}`, _skipAdditionalParameters: true });
56
+ if (data.id !== uri) {
57
+ throw new errors.InvalidRequestError({ message: "Skohub URL did not return expected concept scheme" });
58
+ }
59
+ const { title, preferredNamespaceUri, hasTopConcept, description } = data;
60
+ scheme = { uri, identifier: uris.filter((u) => u !== uri) };
61
+ scheme.prefLabel = title;
62
+ Object.keys(scheme.prefLabel || {}).forEach((key) => {
63
+ scheme.prefLabel[key] = decodeUnicode(scheme.prefLabel[key]);
64
+ });
65
+ scheme.namespace = preferredNamespaceUri;
66
+ scheme.topConcepts = (hasTopConcept || []).map((c) => this._toJskosConcept(c));
67
+ scheme.concepts = [null];
68
+ if (description) {
69
+ scheme.definition = description;
70
+ Object.keys(scheme.definition).forEach((key) => {
71
+ scheme.definition[key] = [decodeUnicode(scheme.definition[key])];
72
+ });
73
+ }
74
+ for (let key of Object.keys(scheme).filter((key2) => !scheme[key2])) {
75
+ delete scheme[key];
76
+ }
77
+ for (let uri2 of uris) {
78
+ this._schemeCache[uri2] = scheme;
79
+ }
80
+ return scheme;
81
+ }
82
+ async _loadConcept({ uri, ...config }) {
83
+ if (this._conceptCache[uri]) {
84
+ return this._conceptCache[uri];
85
+ }
86
+ try {
87
+ const data = await this.axios({ ...config, url: `${uri}.json`, _skipAdditionalParameters: true });
88
+ if (data.id !== uri) {
89
+ throw new errors.InvalidRequestError({ message: "Skohub URL did not return expected concept URI" });
90
+ }
91
+ const concept = this._toJskosConcept(data);
92
+ this._conceptCache[uri] = concept;
93
+ return concept;
94
+ } catch (error) {
95
+ return null;
96
+ }
97
+ }
98
+ _toJskosConcept(data) {
99
+ const concept = { uri: data.id };
100
+ concept.prefLabel = data.prefLabel;
101
+ Object.keys(concept.prefLabel || {}).forEach((key) => {
102
+ concept.prefLabel[key] = decodeUnicode(concept.prefLabel[key]);
103
+ });
104
+ concept.narrower = (data.narrower || []).map((c) => this._toJskosConcept(c));
105
+ concept.notation = data.notation || [];
106
+ if (data.broader && data.broader.id) {
107
+ concept.broader = [{ uri: data.broader.id }];
108
+ }
109
+ if (data.inScheme && data.inScheme.id) {
110
+ concept.inScheme = [{ uri: data.inScheme.id }];
111
+ }
112
+ if (data.scopeNote) {
113
+ concept.scopeNote = data.scopeNote;
114
+ Object.keys(concept.scopeNote).forEach((key) => {
115
+ concept.scopeNote[key] = [decodeUnicode(concept.scopeNote[key])];
116
+ });
117
+ }
118
+ return concept;
119
+ }
120
+ async getSchemes({ ...config }) {
121
+ return Promise.all(this.schemes.map((scheme) => this._loadScheme({ ...config, scheme })));
122
+ }
123
+ async getTop({ scheme, ...config }) {
124
+ if (!scheme || !scheme.uri) {
125
+ throw new errors.InvalidOrMissingParameterError({ parameter: "scheme", message: "Missing scheme URI" });
126
+ }
127
+ scheme = await this._loadScheme({ scheme, ...config });
128
+ return scheme.topConcepts || [];
129
+ }
130
+ async getConcepts({ concepts, ...config }) {
131
+ if (!_.isArray(concepts)) {
132
+ concepts = [concepts];
133
+ }
134
+ return (await Promise.all(concepts.map(({ uri }) => this._loadConcept({ ...config, uri })))).filter(Boolean);
135
+ }
136
+ async getAncestors({ concept, ...config }) {
137
+ if (!concept || !concept.uri) {
138
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concept" });
139
+ }
140
+ if (concept.ancestors && concept.ancestors[0] !== null) {
141
+ return concept.ancestors;
142
+ }
143
+ concept = await this._loadConcept({ ...config, uri: concept.uri });
144
+ if (!concept || !concept.broader || !concept.broader.length) {
145
+ return [];
146
+ }
147
+ const broader = concept.broader[0];
148
+ return [broader].concat(await this.getAncestors({ concept: broader, ...config })).map((c) => ({ uri: c.uri }));
149
+ }
150
+ async getNarrower({ concept, ...config }) {
151
+ if (!concept || !concept.uri) {
152
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concept" });
153
+ }
154
+ if (concept.narrower && concept.narrower[0] !== null) {
155
+ return concept.narrower;
156
+ }
157
+ concept = await this._loadConcept({ ...config, uri: concept.uri });
158
+ return concept.narrower;
159
+ }
160
+ async search({ search, scheme, limit = 100 }) {
161
+ scheme = await this._loadScheme({ scheme });
162
+ if (!scheme || !scheme.uri) {
163
+ throw new errors.InvalidOrMissingParameterError({ parameter: "scheme" });
164
+ }
165
+ if (!search) {
166
+ throw new errors.InvalidOrMissingParameterError({ parameter: "search" });
167
+ }
168
+ let index;
169
+ if (!this._index[scheme.uri]) {
170
+ this._index[scheme.uri] = {};
171
+ }
172
+ for (const lang of [""].concat(this.languages)) {
173
+ if (this._index[scheme.uri][lang]) {
174
+ index = this._index[scheme.uri][lang];
175
+ break;
176
+ }
177
+ if (this._index[scheme.uri][lang] === null) {
178
+ continue;
179
+ }
180
+ try {
181
+ let postfix = lang ? `.${lang}.index` : ".index";
182
+ if (scheme.uri.endsWith("/")) {
183
+ postfix = `index${postfix}`;
184
+ }
185
+ const data = await this.axios({ url: `${scheme.uri}${postfix}`, _skipAdditionalParameters: true });
186
+ if (data.length < 100) {
187
+ this._index[scheme.uri][lang] = null;
188
+ continue;
189
+ }
190
+ index = FlexSearch.create();
191
+ index.import(data);
192
+ this._index[scheme.uri][lang] = index;
193
+ break;
194
+ } catch (error) {
195
+ this._index[scheme.uri][lang] = null;
196
+ }
197
+ }
198
+ if (!index) {
199
+ throw new errors.InvalidRequestError({ message: "Could not find search index for any of the available languages " + this.languages.join(",") });
200
+ }
201
+ const result = index.search(search);
202
+ const concepts = await this.getConcepts({ concepts: result.map((uri) => ({ uri })) });
203
+ return concepts.slice(0, limit);
204
+ }
205
+ async suggest(config) {
206
+ config._raw = true;
207
+ const concepts = await this.search(config);
208
+ const result = [config.search, [], [], []];
209
+ for (let concept of concepts) {
210
+ const notation = jskos.notation(concept);
211
+ const label = jskos.prefLabel(concept);
212
+ result[1].push((notation ? notation + " " : "") + label);
213
+ result[2].push("");
214
+ result[3].push(concept.uri);
215
+ }
216
+ if (concepts._totalCount != void 0) {
217
+ result._totalCount = concepts._totalCount;
218
+ } else {
219
+ result._totalCount = concepts.length;
220
+ }
221
+ return result;
222
+ }
223
+ }
224
+ SkohubProvider.providerName = "Skohub";
225
+ SkohubProvider.providerType = "http://bartoc.org/api-type/skohub";
226
+ export {
227
+ SkohubProvider as default
228
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cocoda-sdk",
3
- "version": "3.1.0",
3
+ "version": "3.2.2",
4
4
  "description": "SDK for Cocoda",
5
5
  "main": "dist/cjs/index.cjs",
6
6
  "module": "dist/esm/index.js",
@@ -52,22 +52,23 @@
52
52
  },
53
53
  "homepage": "https://github.com/gbv/cocoda-sdk#readme",
54
54
  "devDependencies": {
55
- "axios-mock-adapter": "^1.20.0",
55
+ "axios-mock-adapter": "^1.21.1",
56
56
  "better-docs": "^2.7.2",
57
- "esbuild": "~0.14.36",
57
+ "esbuild": "~0.14.43",
58
58
  "esbuild-plugin-ifdef": "^1.0.1",
59
- "eslint": "^8.13.0",
59
+ "eslint": "^8.17.0",
60
60
  "eslint-config-gbv": "^1.0.3",
61
- "glob": "^8.0.1",
61
+ "glob": "^8.0.3",
62
62
  "jsdoc": "^3.6.10",
63
63
  "license-checker": "^25.0.1",
64
- "lint-staged": "^12.3.8",
64
+ "lint-staged": "^12.5.0",
65
65
  "mocha": "^9.2.2",
66
66
  "mocha-eslint": "^7.0.0",
67
67
  "pre-commit": "^1.2.2"
68
68
  },
69
69
  "dependencies": {
70
- "axios": "^0.26.1",
70
+ "axios": "~0.26.1",
71
+ "flexsearch": "~0.6.32",
71
72
  "jskos-tools": "^1.0.26",
72
73
  "localforage": "^1.10.0",
73
74
  "lodash": "^4.17.21",