UNPKG

10.5 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('isomorphic-unfetch')) :
3 typeof define === 'function' && define.amd ? define(['exports', 'isomorphic-unfetch'], factory) :
4 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.optimade = {}));
5})(this, (function (exports) { 'use strict';
6
7 function allSettled(promises, catcher = () => null) {
8 return Promise.all(promises.map(promise => promise.catch(catcher)));
9 }
10 async function fetchWithTimeout(url, options = {}, timeout = 5000) {
11 const ac = new AbortController();
12 const signal = ac.signal;
13 const timer = setTimeout(() => ac.abort(), timeout);
14 try {
15 return await fetch(url, { ...options, signal });
16 }
17 catch (err) {
18 if (err.name === 'AbortError') {
19 throw new Error('Request timed out');
20 }
21 throw err;
22 }
23 finally {
24 clearTimeout(timer);
25 }
26 }
27
28 var version = "2.1.0";
29
30 class Optimade {
31 providersUrl = '';
32 corsProxyUrl = '';
33 providers = null;
34 apis = {};
35 reqStack = [];
36 constructor({ providersUrl = '', corsProxyUrl = '' } = {}) {
37 this.corsProxyUrl = corsProxyUrl;
38 this.providersUrl = this.wrapUrl(providersUrl);
39 }
40 async addProvider(provider) {
41 if (!this.apis[provider.id]) {
42 this.apis[provider.id] = [];
43 }
44 try {
45 const ver = provider.attributes
46 && provider.attributes.api_version ?
47 provider.attributes.api_version.charAt(0) : '';
48 const api = await this.getApis(provider, ver ? `v${ver}` : '');
49 if (api.attributes.available_endpoints.includes('structures')) {
50 this.apis[provider.id].push(api);
51 }
52 }
53 catch (ignore) {
54 console.log(ignore);
55 }
56 if (!provider.attributes.query_limits) {
57 const formula = `chemical_formula_anonymous="A2B"`;
58 const url = `${provider.attributes.base_url}/v1/structures?filter=${formula}&page_limit=1000`;
59 try {
60 const res = await fetch(url).then(res => res.json());
61 const version = res.meta && res.meta.api_version || this.apis[provider.id][0].attributes.api_version;
62 const detail = (errors) => {
63 return errors
64 ? errors.length
65 ? errors[0].detail
66 : errors.detail
67 : '10';
68 };
69 const limits = detail(res.errors)
70 .match(/\d+/g)
71 .filter((number) => +number < 1000)
72 .map((number) => +number);
73 provider.attributes = {
74 ...provider.attributes,
75 api_version: version,
76 query_limits: limits
77 };
78 }
79 catch (error) {
80 console.log(error);
81 }
82 }
83 this.providers[provider.id] = provider;
84 return this.providers;
85 }
86 async getProviders(api) {
87 const providers = await (api ?
88 this.followLinks(api).catch(() => null) :
89 Optimade.getJSON(this.providersUrl).catch(() => null));
90 if (!providers) {
91 return null;
92 }
93 if (!this.providers) {
94 this.providers = {};
95 }
96 const data = providers.data.filter(Optimade.isProviderValid);
97 const ver = providers.meta && providers.meta.api_version ?
98 providers.meta.api_version.charAt(0) : '';
99 for (const provider of data) {
100 if (!this.apis[provider.id]) {
101 this.apis[provider.id] = [];
102 }
103 try {
104 const api = await this.getApis(provider, ver ? `v${ver}` : '');
105 if (!api) {
106 continue;
107 }
108 if (api.attributes.available_endpoints.includes('structures')) {
109 this.apis[provider.id].push(api);
110 if (!this.providers[provider.id]) {
111 this.providers[provider.id] = provider;
112 }
113 }
114 else {
115 await this.getProviders(api);
116 }
117 }
118 catch (ignore) {
119 console.log(ignore);
120 }
121 }
122 return this.providers;
123 }
124 async getApis(provider, version = '') {
125 if (typeof provider === 'string') {
126 provider = this.providers[provider];
127 }
128 if (!provider) {
129 throw new Error('No provider found');
130 }
131 const url = this.wrapUrl(`${provider.attributes.base_url}/${version}`, '/info');
132 if (this.isDuplicatedReq(url)) {
133 return null;
134 }
135 const apis = await Optimade.getJSON(url);
136 return Optimade.apiVersion(apis);
137 }
138 async getStructures({ providerId, filter, page, limit, offset }) {
139 if (!this.apis[providerId]) {
140 return null;
141 }
142 const apis = this.apis[providerId].filter(api => api.attributes.available_endpoints.includes('structures'));
143 const provider = this.providers[providerId];
144 const structures = await allSettled(apis.map(async (api) => {
145 const pageLimit = limit ? `&page_limit=${limit}` : '';
146 const pageNumber = page ? `&page_number=${page}` : '';
147 const pageOffset = offset ? `&page_offset=${offset}` : '';
148 const params = filter ? `${pageLimit + pageNumber + pageOffset}` : `?${pageLimit}`;
149 const url = this.wrapUrl(Optimade.apiVersionUrl(api), filter ? `/structures?filter=${filter + params}` : `/structures${params}`);
150 try {
151 return await Optimade.getJSON(url, {}, { Origin: 'https://cors.optimade.science', 'X-Requested-With': 'XMLHttpRequest' });
152 }
153 catch (error) {
154 return error;
155 }
156 }));
157 return structures.reduce((structures, structure) => {
158 console.dir(`optimade-client-${providerId}:`, structure);
159 if (structure instanceof Error || Object.keys(structure).includes('errors')) {
160 return structures.concat(structure);
161 }
162 else {
163 structure.meta.pages = Math.ceil(structure.meta.data_returned / (limit || structure.data.length));
164 structure.meta.limits = provider.attributes.query_limits || [10];
165 return structures.concat(structure);
166 }
167 }, []);
168 }
169 getStructuresAll({ providers, filter, page, limit, offset, batch = true }) {
170 const results = providers.reduce((structures, providerId) => {
171 const provider = this.providers[providerId];
172 if (provider) {
173 structures.push(allSettled([
174 this.getStructures({ providerId, filter, page, limit, offset }),
175 Promise.resolve(provider)
176 ]));
177 }
178 return structures;
179 }, []);
180 return batch ? Promise.all(results) : results;
181 }
182 async followLinks(api) {
183 if (!api.attributes.available_endpoints.includes('links')) {
184 return null;
185 }
186 const url = this.wrapUrl(Optimade.apiVersionUrl(api), '/links');
187 return !this.isDuplicatedReq(url) ? Optimade.getJSON(url) : null;
188 }
189 wrapUrl(url, tail = '') {
190 url = this.corsProxyUrl ? `${this.corsProxyUrl}/${url.replace('://', '/').replace('//', '/')}` : url;
191 return tail ? url.replace(/\/$/, '') + tail : url;
192 }
193 isDuplicatedReq(url) {
194 return this.reqStack.includes(url) || !this.reqStack.unshift(url);
195 }
196 static async getJSON(uri, params = null, headers = {}) {
197 const url = new URL(uri);
198 const timeout = 10000;
199 if (params) {
200 Object.entries(params).forEach((param) => url.searchParams.append(...param));
201 }
202 Object.assign(headers, { 'User-Agent': `tilde-lab-optimade-client/${version}` });
203 const res = await fetchWithTimeout(url.toString(), { headers }, timeout);
204 if (!res.ok) {
205 const err = await res.json();
206 const error = new Error(err.errors[0].detail);
207 error.response = err;
208 throw error;
209 }
210 if (res.status !== 204) {
211 return await res.json();
212 }
213 }
214 static isProviderValid(provider) {
215 return provider.attributes.base_url && !provider.attributes.base_url.includes('example');
216 }
217 static apiVersionUrl({ attributes: { api_version, available_api_versions } }) {
218 let url = available_api_versions[api_version];
219 if (!url && Array.isArray(available_api_versions)) {
220 const api = available_api_versions.find(({ version }) => version === api_version);
221 url = api && api.url;
222 }
223 return url;
224 }
225 static apiVersion({ data, meta }) {
226 return Array.isArray(data) ?
227 data.find(({ attributes }) => attributes.api_version === meta.api_version) :
228 data;
229 }
230 }
231
232 exports.Optimade = Optimade;
233
234 Object.defineProperty(exports, '__esModule', { value: true });
235
236}));