cocoda-sdk 3.0.2 → 3.2.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.
@@ -114,7 +114,8 @@ class CocodaSDK {
114
114
  timer: null,
115
115
  result: null,
116
116
  error: null,
117
- isPaused: false
117
+ isPaused: false,
118
+ interval
118
119
  };
119
120
  const handleResult = (result) => {
120
121
  const previousResult = repeat.result;
@@ -134,7 +135,7 @@ class CocodaSDK {
134
135
  }
135
136
  repeat.timer = setTimeout(() => {
136
137
  toCall();
137
- }, interval);
138
+ }, repeat.interval);
138
139
  };
139
140
  const call = () => asyncFunc().then(handleResult).catch(handleError).then(() => repeatIfNecessary(call));
140
141
  const setup = (_callImmediately = callImmediately) => {
@@ -157,7 +158,7 @@ class CocodaSDK {
157
158
  } else {
158
159
  setTimeout(() => {
159
160
  repeat.timer && clearTimeout(repeat.timer);
160
- }, interval);
161
+ }, repeat.interval);
161
162
  }
162
163
  },
163
164
  get isPaused() {
@@ -168,6 +169,12 @@ class CocodaSDK {
168
169
  },
169
170
  get hasErrored() {
170
171
  return !!repeat.error;
172
+ },
173
+ get interval() {
174
+ return repeat.interval;
175
+ },
176
+ set interval(value) {
177
+ repeat.interval = value;
171
178
  }
172
179
  };
173
180
  }
@@ -177,7 +184,7 @@ class CocodaSDK {
177
184
  if (registry.has.schemes !== false) {
178
185
  let promise = registry.getSchemes(config).then((results) => {
179
186
  for (let scheme of results) {
180
- const currentSchemeRegistry = scheme._registry;
187
+ scheme._registry = registry;
181
188
  scheme.__DETAILSLOADED__ = 1;
182
189
  scheme.type = scheme.type || ["http://www.w3.org/2004/02/skos/core#ConceptScheme"];
183
190
  let otherScheme = schemes.find((s) => jskos.compare(s, scheme)), prio, otherPrio, override = false;
@@ -208,7 +215,7 @@ class CocodaSDK {
208
215
  }
209
216
  scheme = jskos.merge(scheme, _.omit(otherScheme, ["concepts", "topConcepts"]), { mergeUris: true, skipPaths: ["_registry"] });
210
217
  }
211
- scheme._registry = currentSchemeRegistry;
218
+ scheme._registry = registry;
212
219
  schemes.push(scheme);
213
220
  } else {
214
221
  const index = schemes.findIndex((s) => jskos.compare(s, scheme));
@@ -226,6 +233,16 @@ class CocodaSDK {
226
233
  }
227
234
  }
228
235
  await Promise.all(promises);
236
+ schemes.forEach((scheme) => {
237
+ const previousRegistry = scheme._registry;
238
+ delete scheme._registry;
239
+ const newRegistry = this.registryForScheme(scheme);
240
+ if (!newRegistry || newRegistry._api.api === previousRegistry._api.api) {
241
+ scheme._registry = previousRegistry;
242
+ } else {
243
+ scheme._registry = newRegistry;
244
+ }
245
+ });
229
246
  return jskos.sortSchemes(schemes.filter(Boolean));
230
247
  }
231
248
  registryForScheme(scheme) {
@@ -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.");
@@ -315,7 +317,7 @@ class BaseProvider {
315
317
  }
316
318
  const previousRegistry = scheme._registry;
317
319
  scheme._registry = this.cdk && this.cdk.registryForScheme(scheme);
318
- if (!scheme._registry || previousRegistry === scheme._registry) {
320
+ if (!scheme._registry || previousRegistry === scheme._registry || scheme._registry._api.api === this._api.api) {
319
321
  scheme._registry = previousRegistry || this;
320
322
  } else {
321
323
  ["concepts", "topConcepts"].forEach((key) => {
@@ -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
  };
@@ -36,7 +36,13 @@ class MappingsApiProvider extends BaseProvider {
36
36
  this.has.mappings.delete = !!_.get(this._config, "mappings.delete");
37
37
  this.has.mappings.anonymous = !!_.get(this._config, "mappings.anonymous");
38
38
  }
39
- this.has.concordances = !!this._api.concordances;
39
+ this.has.concordances = this._api.concordances ? {} : false;
40
+ if (this.has.concordances) {
41
+ this.has.concordances.read = !!_.get(this._config, "concordances.read");
42
+ this.has.concordances.create = !!_.get(this._config, "concordances.create");
43
+ this.has.concordances.update = !!_.get(this._config, "concordances.update");
44
+ this.has.concordances.delete = !!_.get(this._config, "concordances.delete");
45
+ }
40
46
  this.has.annotations = this._api.annotations ? {} : false;
41
47
  if (this.has.annotations) {
42
48
  this.has.annotations.read = !!_.get(this._config, "annotations.read");
@@ -72,7 +78,7 @@ class MappingsApiProvider extends BaseProvider {
72
78
  throw error;
73
79
  }
74
80
  }
75
- async getMappings({ from, fromScheme, to, toScheme, creator, type, partOf, offset, limit, direction, mode, identifier, 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 }) {
76
82
  let params = {}, url = this._api.mappings;
77
83
  if (from) {
78
84
  params.from = _.isString(from) ? from : from.uri;
@@ -104,6 +110,18 @@ class MappingsApiProvider extends BaseProvider {
104
110
  if (direction) {
105
111
  params.direction = direction;
106
112
  }
113
+ if (cardinality) {
114
+ params.cardinality = cardinality;
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
+ }
107
125
  if (mode) {
108
126
  params.mode = mode;
109
127
  }
@@ -169,8 +187,6 @@ class MappingsApiProvider extends BaseProvider {
169
187
  if (!mapping) {
170
188
  throw new errors.InvalidOrMissingParameterError({ parameter: "mapping" });
171
189
  }
172
- mapping = jskos.minifyMapping(mapping);
173
- mapping = jskos.addMappingIdentifiers(mapping);
174
190
  const uri = mapping.uri;
175
191
  if (!uri || !uri.startsWith(this._api.mappings)) {
176
192
  throw new errors.InvalidOrMissingParameterError({ parameter: "mapping", message: "URI doesn't seem to be part of this registry." });
@@ -179,7 +195,7 @@ class MappingsApiProvider extends BaseProvider {
179
195
  ...config,
180
196
  method: "patch",
181
197
  url: uri,
182
- data: mapping,
198
+ data: _.omit(mapping, "uri"),
183
199
  params: {
184
200
  ...this._defaultParams,
185
201
  ...config.params || {}
@@ -262,6 +278,74 @@ class MappingsApiProvider extends BaseProvider {
262
278
  url: this._api.concordances
263
279
  });
264
280
  }
281
+ async postConcordance({ concordance, ...config }) {
282
+ if (!concordance) {
283
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concordance" });
284
+ }
285
+ return this.axios({
286
+ ...config,
287
+ method: "post",
288
+ url: this._api.concordances,
289
+ data: concordance,
290
+ params: {
291
+ ...this._defaultParams,
292
+ ...config.params || {}
293
+ }
294
+ });
295
+ }
296
+ async putConcordance({ concordance, ...config }) {
297
+ if (!concordance) {
298
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concordance" });
299
+ }
300
+ const uri = concordance.uri;
301
+ if (!uri || !uri.startsWith(this._api.concordances)) {
302
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concordance", message: "URI doesn't seem to be part of this registry." });
303
+ }
304
+ return this.axios({
305
+ ...config,
306
+ method: "put",
307
+ url: uri,
308
+ data: concordance,
309
+ params: {
310
+ ...this._defaultParams,
311
+ ...config.params || {}
312
+ }
313
+ });
314
+ }
315
+ async patchConcordance({ concordance, ...config }) {
316
+ if (!concordance) {
317
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concordance" });
318
+ }
319
+ const uri = concordance.uri;
320
+ if (!uri || !uri.startsWith(this._api.concordances)) {
321
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concordance", message: "URI doesn't seem to be part of this registry." });
322
+ }
323
+ return this.axios({
324
+ ...config,
325
+ method: "patch",
326
+ url: uri,
327
+ data: _.omit(concordance, "uri"),
328
+ params: {
329
+ ...this._defaultParams,
330
+ ...config.params || {}
331
+ }
332
+ });
333
+ }
334
+ async deleteConcordance({ concordance, ...config }) {
335
+ if (!concordance) {
336
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concordance" });
337
+ }
338
+ const uri = concordance.uri;
339
+ if (!uri || !uri.startsWith(this._api.concordances)) {
340
+ throw new errors.InvalidOrMissingParameterError({ parameter: "concordance", message: "URI doesn't seem to be part of this registry." });
341
+ }
342
+ await this.axios({
343
+ ...config,
344
+ method: "delete",
345
+ url: uri
346
+ });
347
+ return true;
348
+ }
265
349
  }
266
350
  MappingsApiProvider.providerName = "MappingsApi";
267
351
  MappingsApiProvider.stored = true;
@@ -0,0 +1,227 @@
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
+ if (!scheme || !scheme.uri) {
162
+ throw new errors.InvalidOrMissingParameterError({ parameter: "scheme" });
163
+ }
164
+ if (!search) {
165
+ throw new errors.InvalidOrMissingParameterError({ parameter: "search" });
166
+ }
167
+ let index;
168
+ if (!this._index[scheme.uri]) {
169
+ this._index[scheme.uri] = {};
170
+ }
171
+ for (const lang of [""].concat(this.languages)) {
172
+ if (this._index[scheme.uri][lang]) {
173
+ index = this._index[scheme.uri][lang];
174
+ break;
175
+ }
176
+ if (this._index[scheme.uri][lang] === null) {
177
+ continue;
178
+ }
179
+ try {
180
+ let postfix = lang ? `.${lang}.index` : ".index";
181
+ if (scheme.uri.endsWith("/")) {
182
+ postfix = `index${postfix}`;
183
+ }
184
+ const data = await this.axios({ url: `${scheme.uri}${postfix}`, _skipAdditionalParameters: true });
185
+ if (data.length < 100) {
186
+ this._index[scheme.uri][lang] = null;
187
+ continue;
188
+ }
189
+ index = FlexSearch.create();
190
+ index.import(data);
191
+ this._index[scheme.uri][lang] = index;
192
+ break;
193
+ } catch (error) {
194
+ this._index[scheme.uri][lang] = null;
195
+ }
196
+ }
197
+ if (!index) {
198
+ throw new errors.InvalidRequestError({ message: "Could not find search index for any of the available languages " + this.languages.join(",") });
199
+ }
200
+ const result = index.search(search);
201
+ const concepts = await this.getConcepts({ concepts: result.map((uri) => ({ uri })) });
202
+ return concepts.slice(0, limit);
203
+ }
204
+ async suggest(config) {
205
+ config._raw = true;
206
+ const concepts = await this.search(config);
207
+ const result = [config.search, [], [], []];
208
+ for (let concept of concepts) {
209
+ const notation = jskos.notation(concept);
210
+ const label = jskos.prefLabel(concept);
211
+ result[1].push((notation ? notation + " " : "") + label);
212
+ result[2].push("");
213
+ result[3].push(concept.uri);
214
+ }
215
+ if (concepts._totalCount != void 0) {
216
+ result._totalCount = concepts._totalCount;
217
+ } else {
218
+ result._totalCount = concepts.length;
219
+ }
220
+ return result;
221
+ }
222
+ }
223
+ SkohubProvider.providerName = "Skohub";
224
+ SkohubProvider.providerType = "http://bartoc.org/api-type/skohub";
225
+ export {
226
+ SkohubProvider as default
227
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cocoda-sdk",
3
- "version": "3.0.2",
3
+ "version": "3.2.0",
4
4
  "description": "SDK for Cocoda",
5
5
  "main": "dist/cjs/index.cjs",
6
6
  "module": "dist/esm/index.js",
@@ -26,7 +26,8 @@
26
26
  "release": "npm run build && npm test && git checkout dev && git pull && npm version $SEMVER && git push && git checkout master && git merge dev && git push --follow-tags && git checkout dev",
27
27
  "release:patch": "SEMVER=patch npm run release",
28
28
  "release:minor": "SEMVER=minor npm run release",
29
- "release:major": "SEMVER=major npm run release"
29
+ "release:major": "SEMVER=major npm run release",
30
+ "postinstall": "[ -d './dist' ] || npm run build"
30
31
  },
31
32
  "lint-staged": {
32
33
  "**/*.js": [
@@ -51,23 +52,24 @@
51
52
  },
52
53
  "homepage": "https://github.com/gbv/cocoda-sdk#readme",
53
54
  "devDependencies": {
54
- "axios-mock-adapter": "^1.20.0",
55
+ "axios-mock-adapter": "^1.21.1",
55
56
  "better-docs": "^2.7.2",
56
- "esbuild": "~0.14.23",
57
+ "esbuild": "~0.14.43",
57
58
  "esbuild-plugin-ifdef": "^1.0.1",
58
- "eslint": "^8.9.0",
59
+ "eslint": "^8.17.0",
59
60
  "eslint-config-gbv": "^1.0.3",
60
- "glob": "^7.2.0",
61
+ "glob": "^8.0.3",
61
62
  "jsdoc": "^3.6.10",
62
63
  "license-checker": "^25.0.1",
63
- "lint-staged": "^12.3.4",
64
- "mocha": "^9.2.1",
64
+ "lint-staged": "^12.5.0",
65
+ "mocha": "^9.2.2",
65
66
  "mocha-eslint": "^7.0.0",
66
67
  "pre-commit": "^1.2.2"
67
68
  },
68
69
  "dependencies": {
69
- "axios": "^0.26.0",
70
- "jskos-tools": "^1.0.25",
70
+ "axios": "~0.26.1",
71
+ "flexsearch": "~0.6.32",
72
+ "jskos-tools": "^1.0.26",
71
73
  "localforage": "^1.10.0",
72
74
  "lodash": "^4.17.21",
73
75
  "uuid": "^8.3.2"