@algolia/client-common 5.0.0-alpha.11 → 5.0.0-alpha.111
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/dist/{client-common.cjs.js → client-common.cjs} +190 -213
- package/dist/client-common.esm.node.js +190 -212
- package/dist/index.d.ts +9 -9
- package/dist/src/cache/createBrowserLocalStorageCache.d.ts +2 -2
- package/dist/src/cache/createBrowserLocalStorageCache.d.ts.map +1 -1
- package/dist/src/cache/createFallbackableCache.d.ts +2 -2
- package/dist/src/cache/createMemoryCache.d.ts +2 -2
- package/dist/src/cache/createNullCache.d.ts +2 -2
- package/dist/src/cache/index.d.ts +4 -4
- package/dist/src/constants.d.ts +6 -6
- package/dist/src/createAlgoliaAgent.d.ts +2 -2
- package/dist/src/createAuth.d.ts +5 -5
- package/dist/src/createEchoRequester.d.ts +6 -6
- package/dist/src/createEchoRequester.d.ts.map +1 -1
- package/dist/src/createIterablePromise.d.ts +12 -12
- package/dist/src/getAlgoliaAgent.d.ts +7 -7
- package/dist/src/getAlgoliaAgent.d.ts.map +1 -1
- package/dist/src/transporter/createStatefulHost.d.ts +2 -2
- package/dist/src/transporter/createTransporter.d.ts +2 -2
- package/dist/src/transporter/errors.d.ts +37 -20
- package/dist/src/transporter/errors.d.ts.map +1 -1
- package/dist/src/transporter/helpers.d.ts +8 -8
- package/dist/src/transporter/helpers.d.ts.map +1 -1
- package/dist/src/transporter/index.d.ts +6 -6
- package/dist/src/transporter/responses.d.ts +4 -4
- package/dist/src/transporter/stackTrace.d.ts +3 -3
- package/dist/src/types/cache.d.ts +60 -46
- package/dist/src/types/cache.d.ts.map +1 -1
- package/dist/src/types/createClient.d.ts +11 -11
- package/dist/src/types/createClient.d.ts.map +1 -1
- package/dist/src/types/createIterablePromise.d.ts +35 -35
- package/dist/src/types/createIterablePromise.d.ts.map +1 -1
- package/dist/src/types/host.d.ts +36 -32
- package/dist/src/types/host.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +6 -6
- package/dist/src/types/requester.d.ts +65 -65
- package/dist/src/types/requester.d.ts.map +1 -1
- package/dist/src/types/transporter.d.ts +127 -127
- package/dist/src/types/transporter.d.ts.map +1 -1
- package/package.json +11 -8
- package/src/__tests__/cache/browser-local-storage-cache.test.ts +61 -9
- package/src/cache/createBrowserLocalStorageCache.ts +55 -6
- package/src/createEchoRequester.ts +24 -9
- package/src/transporter/errors.ts +39 -3
- package/src/transporter/helpers.ts +16 -8
- package/src/types/cache.ts +17 -0
- package/src/types/host.ts +5 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
3
|
function createAuth(appId, apiKey, authMode = 'WithinHeaders') {
|
|
6
4
|
const credentials = {
|
|
7
5
|
'x-algolia-api-key': apiKey,
|
|
@@ -11,30 +9,39 @@ function createAuth(appId, apiKey, authMode = 'WithinHeaders') {
|
|
|
11
9
|
headers() {
|
|
12
10
|
return authMode === 'WithinHeaders' ? credentials : {};
|
|
13
11
|
},
|
|
14
|
-
|
|
15
12
|
queryParameters() {
|
|
16
13
|
return authMode === 'WithinQueryParameters' ? credentials : {};
|
|
17
14
|
}
|
|
18
|
-
|
|
19
15
|
};
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
function getUrlParams({
|
|
23
19
|
host,
|
|
24
|
-
|
|
20
|
+
search,
|
|
25
21
|
pathname
|
|
26
22
|
}) {
|
|
27
|
-
const
|
|
23
|
+
const urlSearchParams = search.split('?');
|
|
24
|
+
if (urlSearchParams.length === 1) {
|
|
25
|
+
return {
|
|
26
|
+
host,
|
|
27
|
+
algoliaAgent: '',
|
|
28
|
+
searchParams: undefined,
|
|
29
|
+
path: pathname
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const splitSearchParams = urlSearchParams[1].split('&');
|
|
33
|
+
let algoliaAgent = '';
|
|
28
34
|
const searchParams = {};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (splitSearchParams.length > 0) {
|
|
36
|
+
splitSearchParams.forEach(param => {
|
|
37
|
+
const [key, value] = param.split('=');
|
|
38
|
+
if (key === 'x-algolia-agent') {
|
|
39
|
+
algoliaAgent = value;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
searchParams[key] = value;
|
|
43
|
+
});
|
|
36
44
|
}
|
|
37
|
-
|
|
38
45
|
return {
|
|
39
46
|
host,
|
|
40
47
|
algoliaAgent,
|
|
@@ -42,7 +49,6 @@ function getUrlParams({
|
|
|
42
49
|
path: pathname
|
|
43
50
|
};
|
|
44
51
|
}
|
|
45
|
-
|
|
46
52
|
function createEchoRequester({
|
|
47
53
|
getURL,
|
|
48
54
|
status = 200
|
|
@@ -54,11 +60,12 @@ function createEchoRequester({
|
|
|
54
60
|
algoliaAgent,
|
|
55
61
|
path
|
|
56
62
|
} = getUrlParams(getURL(request.url));
|
|
57
|
-
const content = {
|
|
63
|
+
const content = {
|
|
64
|
+
...request,
|
|
58
65
|
data: request.data ? JSON.parse(request.data) : undefined,
|
|
59
66
|
path,
|
|
60
67
|
host,
|
|
61
|
-
algoliaAgent
|
|
68
|
+
algoliaAgent,
|
|
62
69
|
searchParams
|
|
63
70
|
};
|
|
64
71
|
return Promise.resolve({
|
|
@@ -67,21 +74,20 @@ function createEchoRequester({
|
|
|
67
74
|
status
|
|
68
75
|
});
|
|
69
76
|
}
|
|
70
|
-
|
|
71
77
|
return {
|
|
72
78
|
send
|
|
73
79
|
};
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
/**
|
|
77
|
-
* Helper: Returns the promise of a given `func` to iterate on, based on a given `validate` condition.
|
|
78
|
-
*
|
|
79
|
-
* @param createIterator - The createIterator options.
|
|
80
|
-
* @param createIterator.func - The function to run, which returns a promise.
|
|
81
|
-
* @param createIterator.validate - The validator function. It receives the resolved return of `func`.
|
|
82
|
-
* @param createIterator.aggregator - The function that runs right after the `func` method has been executed, allows you to do anything with the response before `validate`.
|
|
83
|
-
* @param createIterator.error - The `validate` condition to throw an error, and its message.
|
|
84
|
-
* @param createIterator.timeout - The function to decide how long to wait between iterations.
|
|
82
|
+
/**
|
|
83
|
+
* Helper: Returns the promise of a given `func` to iterate on, based on a given `validate` condition.
|
|
84
|
+
*
|
|
85
|
+
* @param createIterator - The createIterator options.
|
|
86
|
+
* @param createIterator.func - The function to run, which returns a promise.
|
|
87
|
+
* @param createIterator.validate - The validator function. It receives the resolved return of `func`.
|
|
88
|
+
* @param createIterator.aggregator - The function that runs right after the `func` method has been executed, allows you to do anything with the response before `validate`.
|
|
89
|
+
* @param createIterator.error - The `validate` condition to throw an error, and its message.
|
|
90
|
+
* @param createIterator.timeout - The function to decide how long to wait between iterations.
|
|
85
91
|
*/
|
|
86
92
|
function createIterablePromise({
|
|
87
93
|
func,
|
|
@@ -96,15 +102,12 @@ function createIterablePromise({
|
|
|
96
102
|
if (aggregator) {
|
|
97
103
|
aggregator(response);
|
|
98
104
|
}
|
|
99
|
-
|
|
100
105
|
if (validate(response)) {
|
|
101
106
|
return resolve(response);
|
|
102
107
|
}
|
|
103
|
-
|
|
104
108
|
if (error && error.validate(response)) {
|
|
105
109
|
return reject(new Error(error.message(response)));
|
|
106
110
|
}
|
|
107
|
-
|
|
108
111
|
return setTimeout(() => {
|
|
109
112
|
retry(response).then(resolve).catch(reject);
|
|
110
113
|
}, timeout());
|
|
@@ -113,49 +116,66 @@ function createIterablePromise({
|
|
|
113
116
|
});
|
|
114
117
|
});
|
|
115
118
|
};
|
|
116
|
-
|
|
117
119
|
return retry();
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
function createBrowserLocalStorageCache(options) {
|
|
121
|
-
let storage;
|
|
122
|
-
|
|
123
|
+
let storage;
|
|
124
|
+
// We've changed the namespace to avoid conflicts with v4, as this version is a huge breaking change
|
|
123
125
|
const namespaceKey = `algolia-client-js-${options.key}`;
|
|
124
|
-
|
|
125
126
|
function getStorage() {
|
|
126
127
|
if (storage === undefined) {
|
|
127
128
|
storage = options.localStorage || window.localStorage;
|
|
128
129
|
}
|
|
129
|
-
|
|
130
130
|
return storage;
|
|
131
131
|
}
|
|
132
|
-
|
|
133
132
|
function getNamespace() {
|
|
134
133
|
return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
|
|
135
134
|
}
|
|
136
|
-
|
|
135
|
+
function setNamespace(namespace) {
|
|
136
|
+
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
137
|
+
}
|
|
138
|
+
function removeOutdatedCacheItems() {
|
|
139
|
+
const timeToLive = options.timeToLive ? options.timeToLive * 1000 : null;
|
|
140
|
+
const namespace = getNamespace();
|
|
141
|
+
const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries(Object.entries(namespace).filter(([, cacheItem]) => {
|
|
142
|
+
return cacheItem.timestamp !== undefined;
|
|
143
|
+
}));
|
|
144
|
+
setNamespace(filteredNamespaceWithoutOldFormattedCacheItems);
|
|
145
|
+
if (!timeToLive) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const filteredNamespaceWithoutExpiredItems = Object.fromEntries(Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => {
|
|
149
|
+
const currentTimestamp = new Date().getTime();
|
|
150
|
+
const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;
|
|
151
|
+
return !isExpired;
|
|
152
|
+
}));
|
|
153
|
+
setNamespace(filteredNamespaceWithoutExpiredItems);
|
|
154
|
+
}
|
|
137
155
|
return {
|
|
138
156
|
get(key, defaultValue, events = {
|
|
139
157
|
miss: () => Promise.resolve()
|
|
140
158
|
}) {
|
|
141
159
|
return Promise.resolve().then(() => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
160
|
+
removeOutdatedCacheItems();
|
|
161
|
+
return getNamespace()[JSON.stringify(key)];
|
|
162
|
+
}).then(value => {
|
|
163
|
+
return Promise.all([value ? value.value : defaultValue(), value !== undefined]);
|
|
145
164
|
}).then(([value, exists]) => {
|
|
146
165
|
return Promise.all([value, exists || events.miss(value)]);
|
|
147
166
|
}).then(([value]) => value);
|
|
148
167
|
},
|
|
149
|
-
|
|
150
168
|
set(key, value) {
|
|
151
169
|
return Promise.resolve().then(() => {
|
|
152
170
|
const namespace = getNamespace();
|
|
153
|
-
namespace[JSON.stringify(key)] =
|
|
171
|
+
namespace[JSON.stringify(key)] = {
|
|
172
|
+
timestamp: new Date().getTime(),
|
|
173
|
+
value
|
|
174
|
+
};
|
|
154
175
|
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
155
176
|
return value;
|
|
156
177
|
});
|
|
157
178
|
},
|
|
158
|
-
|
|
159
179
|
delete(key) {
|
|
160
180
|
return Promise.resolve().then(() => {
|
|
161
181
|
const namespace = getNamespace();
|
|
@@ -163,13 +183,11 @@ function createBrowserLocalStorageCache(options) {
|
|
|
163
183
|
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
164
184
|
});
|
|
165
185
|
},
|
|
166
|
-
|
|
167
186
|
clear() {
|
|
168
187
|
return Promise.resolve().then(() => {
|
|
169
188
|
getStorage().removeItem(namespaceKey);
|
|
170
189
|
});
|
|
171
190
|
}
|
|
172
|
-
|
|
173
191
|
};
|
|
174
192
|
}
|
|
175
193
|
|
|
@@ -181,30 +199,24 @@ function createNullCache() {
|
|
|
181
199
|
const value = defaultValue();
|
|
182
200
|
return value.then(result => Promise.all([result, events.miss(result)])).then(([result]) => result);
|
|
183
201
|
},
|
|
184
|
-
|
|
185
202
|
set(_key, value) {
|
|
186
203
|
return Promise.resolve(value);
|
|
187
204
|
},
|
|
188
|
-
|
|
189
205
|
delete(_key) {
|
|
190
206
|
return Promise.resolve();
|
|
191
207
|
},
|
|
192
|
-
|
|
193
208
|
clear() {
|
|
194
209
|
return Promise.resolve();
|
|
195
210
|
}
|
|
196
|
-
|
|
197
211
|
};
|
|
198
212
|
}
|
|
199
213
|
|
|
200
214
|
function createFallbackableCache(options) {
|
|
201
215
|
const caches = [...options.caches];
|
|
202
216
|
const current = caches.shift();
|
|
203
|
-
|
|
204
217
|
if (current === undefined) {
|
|
205
218
|
return createNullCache();
|
|
206
219
|
}
|
|
207
|
-
|
|
208
220
|
return {
|
|
209
221
|
get(key, defaultValue, events = {
|
|
210
222
|
miss: () => Promise.resolve()
|
|
@@ -215,7 +227,6 @@ function createFallbackableCache(options) {
|
|
|
215
227
|
}).get(key, defaultValue, events);
|
|
216
228
|
});
|
|
217
229
|
},
|
|
218
|
-
|
|
219
230
|
set(key, value) {
|
|
220
231
|
return current.set(key, value).catch(() => {
|
|
221
232
|
return createFallbackableCache({
|
|
@@ -223,7 +234,6 @@ function createFallbackableCache(options) {
|
|
|
223
234
|
}).set(key, value);
|
|
224
235
|
});
|
|
225
236
|
},
|
|
226
|
-
|
|
227
237
|
delete(key) {
|
|
228
238
|
return current.delete(key).catch(() => {
|
|
229
239
|
return createFallbackableCache({
|
|
@@ -231,7 +241,6 @@ function createFallbackableCache(options) {
|
|
|
231
241
|
}).delete(key);
|
|
232
242
|
});
|
|
233
243
|
},
|
|
234
|
-
|
|
235
244
|
clear() {
|
|
236
245
|
return current.clear().catch(() => {
|
|
237
246
|
return createFallbackableCache({
|
|
@@ -239,7 +248,6 @@ function createFallbackableCache(options) {
|
|
|
239
248
|
}).clear();
|
|
240
249
|
});
|
|
241
250
|
}
|
|
242
|
-
|
|
243
251
|
};
|
|
244
252
|
}
|
|
245
253
|
|
|
@@ -252,30 +260,24 @@ function createMemoryCache(options = {
|
|
|
252
260
|
miss: () => Promise.resolve()
|
|
253
261
|
}) {
|
|
254
262
|
const keyAsString = JSON.stringify(key);
|
|
255
|
-
|
|
256
263
|
if (keyAsString in cache) {
|
|
257
264
|
return Promise.resolve(options.serializable ? JSON.parse(cache[keyAsString]) : cache[keyAsString]);
|
|
258
265
|
}
|
|
259
|
-
|
|
260
266
|
const promise = defaultValue();
|
|
261
267
|
return promise.then(value => events.miss(value)).then(() => promise);
|
|
262
268
|
},
|
|
263
|
-
|
|
264
269
|
set(key, value) {
|
|
265
270
|
cache[JSON.stringify(key)] = options.serializable ? JSON.stringify(value) : value;
|
|
266
271
|
return Promise.resolve(value);
|
|
267
272
|
},
|
|
268
|
-
|
|
269
273
|
delete(key) {
|
|
270
274
|
delete cache[JSON.stringify(key)];
|
|
271
275
|
return Promise.resolve();
|
|
272
276
|
},
|
|
273
|
-
|
|
274
277
|
clear() {
|
|
275
278
|
cache = {};
|
|
276
279
|
return Promise.resolve();
|
|
277
280
|
}
|
|
278
|
-
|
|
279
281
|
};
|
|
280
282
|
}
|
|
281
283
|
|
|
@@ -284,16 +286,14 @@ function createMemoryCache(options = {
|
|
|
284
286
|
const EXPIRATION_DELAY = 2 * 60 * 1000;
|
|
285
287
|
function createStatefulHost(host, status = 'up') {
|
|
286
288
|
const lastUpdate = Date.now();
|
|
287
|
-
|
|
288
289
|
function isUp() {
|
|
289
290
|
return status === 'up' || Date.now() - lastUpdate > EXPIRATION_DELAY;
|
|
290
291
|
}
|
|
291
|
-
|
|
292
292
|
function isTimedOut() {
|
|
293
293
|
return status === 'timed out' && Date.now() - lastUpdate <= EXPIRATION_DELAY;
|
|
294
294
|
}
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
return {
|
|
296
|
+
...host,
|
|
297
297
|
status,
|
|
298
298
|
lastUpdate,
|
|
299
299
|
isUp,
|
|
@@ -301,7 +301,22 @@ function createStatefulHost(host, status = 'up') {
|
|
|
301
301
|
};
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
function _toPrimitive(t, r) {
|
|
305
|
+
if ("object" != typeof t || !t) return t;
|
|
306
|
+
var e = t[Symbol.toPrimitive];
|
|
307
|
+
if (void 0 !== e) {
|
|
308
|
+
var i = e.call(t, r || "default");
|
|
309
|
+
if ("object" != typeof i) return i;
|
|
310
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
311
|
+
}
|
|
312
|
+
return ("string" === r ? String : Number)(t);
|
|
313
|
+
}
|
|
314
|
+
function _toPropertyKey(t) {
|
|
315
|
+
var i = _toPrimitive(t, "string");
|
|
316
|
+
return "symbol" == typeof i ? i : i + "";
|
|
317
|
+
}
|
|
304
318
|
function _defineProperty(obj, key, value) {
|
|
319
|
+
key = _toPropertyKey(key);
|
|
305
320
|
if (key in obj) {
|
|
306
321
|
Object.defineProperty(obj, key, {
|
|
307
322
|
value: value,
|
|
@@ -312,92 +327,82 @@ function _defineProperty(obj, key, value) {
|
|
|
312
327
|
} else {
|
|
313
328
|
obj[key] = value;
|
|
314
329
|
}
|
|
315
|
-
|
|
316
330
|
return obj;
|
|
317
331
|
}
|
|
318
332
|
|
|
319
333
|
class AlgoliaError extends Error {
|
|
320
334
|
constructor(message, name) {
|
|
321
335
|
super(message);
|
|
322
|
-
|
|
323
336
|
_defineProperty(this, "name", 'AlgoliaError');
|
|
324
|
-
|
|
325
337
|
if (name) {
|
|
326
338
|
this.name = name;
|
|
327
339
|
}
|
|
328
340
|
}
|
|
329
|
-
|
|
330
341
|
}
|
|
331
342
|
class ErrorWithStackTrace extends AlgoliaError {
|
|
332
343
|
constructor(message, stackTrace, name) {
|
|
333
|
-
super(message, name);
|
|
334
|
-
|
|
344
|
+
super(message, name);
|
|
345
|
+
// the array and object should be frozen to reflect the stackTrace at the time of the error
|
|
335
346
|
_defineProperty(this, "stackTrace", void 0);
|
|
336
|
-
|
|
337
347
|
this.stackTrace = stackTrace;
|
|
338
348
|
}
|
|
339
|
-
|
|
340
349
|
}
|
|
341
350
|
class RetryError extends ErrorWithStackTrace {
|
|
342
351
|
constructor(stackTrace) {
|
|
343
|
-
super('Unreachable hosts - your application id may be incorrect. If the error persists,
|
|
352
|
+
super('Unreachable hosts - your application id may be incorrect. If the error persists, please create a ticket at https://support.algolia.com/ sharing steps we can use to reproduce the issue.', stackTrace, 'RetryError');
|
|
344
353
|
}
|
|
345
|
-
|
|
346
354
|
}
|
|
347
355
|
class ApiError extends ErrorWithStackTrace {
|
|
348
|
-
constructor(message, status, stackTrace) {
|
|
349
|
-
super(message, stackTrace,
|
|
350
|
-
|
|
356
|
+
constructor(message, status, stackTrace, name = 'ApiError') {
|
|
357
|
+
super(message, stackTrace, name);
|
|
351
358
|
_defineProperty(this, "status", void 0);
|
|
352
|
-
|
|
353
359
|
this.status = status;
|
|
354
360
|
}
|
|
355
|
-
|
|
356
361
|
}
|
|
357
362
|
class DeserializationError extends AlgoliaError {
|
|
358
363
|
constructor(message, response) {
|
|
359
364
|
super(message, 'DeserializationError');
|
|
360
|
-
|
|
361
365
|
_defineProperty(this, "response", void 0);
|
|
362
|
-
|
|
363
366
|
this.response = response;
|
|
364
367
|
}
|
|
365
|
-
|
|
368
|
+
}
|
|
369
|
+
// DetailedApiError is only used by the ingestion client to return more informative error, other clients will use ApiClient.
|
|
370
|
+
class DetailedApiError extends ApiError {
|
|
371
|
+
constructor(message, status, error, stackTrace) {
|
|
372
|
+
super(message, status, stackTrace, 'DetailedApiError');
|
|
373
|
+
_defineProperty(this, "error", void 0);
|
|
374
|
+
this.error = error;
|
|
375
|
+
}
|
|
366
376
|
}
|
|
367
377
|
|
|
368
378
|
function shuffle(array) {
|
|
369
379
|
const shuffledArray = array;
|
|
370
|
-
|
|
371
380
|
for (let c = array.length - 1; c > 0; c--) {
|
|
372
381
|
const b = Math.floor(Math.random() * (c + 1));
|
|
373
382
|
const a = array[c];
|
|
374
383
|
shuffledArray[c] = array[b];
|
|
375
384
|
shuffledArray[b] = a;
|
|
376
385
|
}
|
|
377
|
-
|
|
378
386
|
return shuffledArray;
|
|
379
387
|
}
|
|
380
388
|
function serializeUrl(host, path, queryParameters) {
|
|
381
389
|
const queryParametersAsString = serializeQueryParameters(queryParameters);
|
|
382
|
-
let url = `${host.protocol}://${host.url}/${path.charAt(0) === '/' ? path.
|
|
383
|
-
|
|
390
|
+
let url = `${host.protocol}://${host.url}${host.port ? `:${host.port}` : ''}/${path.charAt(0) === '/' ? path.substring(1) : path}`;
|
|
384
391
|
if (queryParametersAsString.length) {
|
|
385
392
|
url += `?${queryParametersAsString}`;
|
|
386
393
|
}
|
|
387
|
-
|
|
388
394
|
return url;
|
|
389
395
|
}
|
|
390
396
|
function serializeQueryParameters(parameters) {
|
|
391
397
|
const isObjectOrArray = value => Object.prototype.toString.call(value) === '[object Object]' || Object.prototype.toString.call(value) === '[object Array]';
|
|
392
|
-
|
|
393
|
-
return Object.keys(parameters).map(key => `${key}=${isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key]}`).join('&');
|
|
398
|
+
return Object.keys(parameters).map(key => `${key}=${encodeURIComponent(isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key]).replaceAll('+', '%20')}`).join('&');
|
|
394
399
|
}
|
|
395
400
|
function serializeData(request, requestOptions) {
|
|
396
401
|
if (request.method === 'GET' || request.data === undefined && requestOptions.data === undefined) {
|
|
397
402
|
return undefined;
|
|
398
403
|
}
|
|
399
|
-
|
|
400
|
-
|
|
404
|
+
const data = Array.isArray(request.data) ? request.data : {
|
|
405
|
+
...request.data,
|
|
401
406
|
...requestOptions.data
|
|
402
407
|
};
|
|
403
408
|
return JSON.stringify(data);
|
|
@@ -427,14 +432,16 @@ function deserializeFailure({
|
|
|
427
432
|
content,
|
|
428
433
|
status
|
|
429
434
|
}, stackFrame) {
|
|
430
|
-
let message = content;
|
|
431
|
-
|
|
432
435
|
try {
|
|
433
|
-
|
|
434
|
-
|
|
436
|
+
const parsed = JSON.parse(content);
|
|
437
|
+
if ('error' in parsed) {
|
|
438
|
+
return new DetailedApiError(parsed.message, status, parsed.error, stackFrame);
|
|
439
|
+
}
|
|
440
|
+
return new ApiError(parsed.message, status, stackFrame);
|
|
441
|
+
} catch (e) {
|
|
442
|
+
// ..
|
|
435
443
|
}
|
|
436
|
-
|
|
437
|
-
return new ApiError(message, status, stackFrame);
|
|
444
|
+
return new ApiError(content, status, stackFrame);
|
|
438
445
|
}
|
|
439
446
|
|
|
440
447
|
function isNetworkError({
|
|
@@ -465,9 +472,12 @@ function stackFrameWithoutCredentials(stackFrame) {
|
|
|
465
472
|
const modifiedHeaders = stackFrame.request.headers['x-algolia-api-key'] ? {
|
|
466
473
|
'x-algolia-api-key': '*****'
|
|
467
474
|
} : {};
|
|
468
|
-
return {
|
|
469
|
-
|
|
470
|
-
|
|
475
|
+
return {
|
|
476
|
+
...stackFrame,
|
|
477
|
+
request: {
|
|
478
|
+
...stackFrame.request,
|
|
479
|
+
headers: {
|
|
480
|
+
...stackFrame.request.headers,
|
|
471
481
|
...modifiedHeaders
|
|
472
482
|
}
|
|
473
483
|
}
|
|
@@ -492,53 +502,49 @@ function createTransporter({
|
|
|
492
502
|
});
|
|
493
503
|
}));
|
|
494
504
|
const hostsUp = statefulHosts.filter(host => host.isUp());
|
|
495
|
-
const hostsTimedOut = statefulHosts.filter(host => host.isTimedOut());
|
|
496
|
-
|
|
505
|
+
const hostsTimedOut = statefulHosts.filter(host => host.isTimedOut());
|
|
506
|
+
// Note, we put the hosts that previously timed out on the end of the list.
|
|
497
507
|
const hostsAvailable = [...hostsUp, ...hostsTimedOut];
|
|
498
508
|
const compatibleHostsAvailable = hostsAvailable.length > 0 ? hostsAvailable : compatibleHosts;
|
|
499
509
|
return {
|
|
500
510
|
hosts: compatibleHostsAvailable,
|
|
501
|
-
|
|
502
511
|
getTimeout(timeoutsCount, baseTimeout) {
|
|
503
|
-
/**
|
|
504
|
-
* Imagine that you have 4 hosts, if timeouts will increase
|
|
505
|
-
* on the following way: 1 (timed out) > 4 (timed out) > 5 (200).
|
|
506
|
-
*
|
|
507
|
-
* Note that, the very next request, we start from the previous timeout.
|
|
508
|
-
*
|
|
509
|
-
* 5 (timed out) > 6 (timed out) > 7 ...
|
|
510
|
-
*
|
|
511
|
-
* This strategy may need to be reviewed, but is the strategy on the our
|
|
512
|
-
* current v3 version.
|
|
512
|
+
/**
|
|
513
|
+
* Imagine that you have 4 hosts, if timeouts will increase
|
|
514
|
+
* on the following way: 1 (timed out) > 4 (timed out) > 5 (200).
|
|
515
|
+
*
|
|
516
|
+
* Note that, the very next request, we start from the previous timeout.
|
|
517
|
+
*
|
|
518
|
+
* 5 (timed out) > 6 (timed out) > 7 ...
|
|
519
|
+
*
|
|
520
|
+
* This strategy may need to be reviewed, but is the strategy on the our
|
|
521
|
+
* current v3 version.
|
|
513
522
|
*/
|
|
514
523
|
const timeoutMultiplier = hostsTimedOut.length === 0 && timeoutsCount === 0 ? 1 : hostsTimedOut.length + 3 + timeoutsCount;
|
|
515
524
|
return timeoutMultiplier * baseTimeout;
|
|
516
525
|
}
|
|
517
|
-
|
|
518
526
|
};
|
|
519
527
|
}
|
|
520
|
-
|
|
521
528
|
async function retryableRequest(request, requestOptions, isRead = true) {
|
|
522
529
|
const stackTrace = [];
|
|
523
|
-
/**
|
|
524
|
-
* First we prepare the payload that do not depend from hosts.
|
|
530
|
+
/**
|
|
531
|
+
* First we prepare the payload that do not depend from hosts.
|
|
525
532
|
*/
|
|
526
|
-
|
|
527
533
|
const data = serializeData(request, requestOptions);
|
|
528
|
-
const headers = serializeHeaders(baseHeaders, request.headers, requestOptions.headers);
|
|
529
|
-
|
|
530
|
-
const dataQueryParameters = request.method === 'GET' ? {
|
|
534
|
+
const headers = serializeHeaders(baseHeaders, request.headers, requestOptions.headers);
|
|
535
|
+
// On `GET`, the data is proxied to query parameters.
|
|
536
|
+
const dataQueryParameters = request.method === 'GET' ? {
|
|
537
|
+
...request.data,
|
|
531
538
|
...requestOptions.data
|
|
532
539
|
} : {};
|
|
533
|
-
const queryParameters = {
|
|
540
|
+
const queryParameters = {
|
|
541
|
+
...baseQueryParameters,
|
|
534
542
|
...request.queryParameters,
|
|
535
543
|
...dataQueryParameters
|
|
536
544
|
};
|
|
537
|
-
|
|
538
545
|
if (algoliaAgent.value) {
|
|
539
546
|
queryParameters['x-algolia-agent'] = algoliaAgent.value;
|
|
540
547
|
}
|
|
541
|
-
|
|
542
548
|
if (requestOptions && requestOptions.queryParameters) {
|
|
543
549
|
for (const key of Object.keys(requestOptions.queryParameters)) {
|
|
544
550
|
// We want to keep `undefined` and `null` values,
|
|
@@ -551,25 +557,19 @@ function createTransporter({
|
|
|
551
557
|
}
|
|
552
558
|
}
|
|
553
559
|
}
|
|
554
|
-
|
|
555
560
|
let timeoutsCount = 0;
|
|
556
|
-
|
|
557
561
|
const retry = async (retryableHosts, getTimeout) => {
|
|
558
|
-
/**
|
|
559
|
-
* We iterate on each host, until there is no host left.
|
|
562
|
+
/**
|
|
563
|
+
* We iterate on each host, until there is no host left.
|
|
560
564
|
*/
|
|
561
565
|
const host = retryableHosts.pop();
|
|
562
|
-
|
|
563
566
|
if (host === undefined) {
|
|
564
567
|
throw new RetryError(stackTraceWithoutCredentials(stackTrace));
|
|
565
568
|
}
|
|
566
|
-
|
|
567
569
|
let responseTimeout = requestOptions.timeout;
|
|
568
|
-
|
|
569
570
|
if (responseTimeout === undefined) {
|
|
570
571
|
responseTimeout = isRead ? timeouts.read : timeouts.write;
|
|
571
572
|
}
|
|
572
|
-
|
|
573
573
|
const payload = {
|
|
574
574
|
data,
|
|
575
575
|
headers,
|
|
@@ -578,12 +578,11 @@ function createTransporter({
|
|
|
578
578
|
connectTimeout: getTimeout(timeoutsCount, timeouts.connect),
|
|
579
579
|
responseTimeout: getTimeout(timeoutsCount, responseTimeout)
|
|
580
580
|
};
|
|
581
|
-
/**
|
|
582
|
-
* The stackFrame is pushed to the stackTrace so we
|
|
583
|
-
* can have information about onRetry and onFailure
|
|
584
|
-
* decisions.
|
|
581
|
+
/**
|
|
582
|
+
* The stackFrame is pushed to the stackTrace so we
|
|
583
|
+
* can have information about onRetry and onFailure
|
|
584
|
+
* decisions.
|
|
585
585
|
*/
|
|
586
|
-
|
|
587
586
|
const pushToStackTrace = response => {
|
|
588
587
|
const stackFrame = {
|
|
589
588
|
request: payload,
|
|
@@ -594,102 +593,85 @@ function createTransporter({
|
|
|
594
593
|
stackTrace.push(stackFrame);
|
|
595
594
|
return stackFrame;
|
|
596
595
|
};
|
|
597
|
-
|
|
598
596
|
const response = await requester.send(payload);
|
|
599
|
-
|
|
600
597
|
if (isRetryable(response)) {
|
|
601
|
-
const stackFrame = pushToStackTrace(response);
|
|
602
|
-
|
|
598
|
+
const stackFrame = pushToStackTrace(response);
|
|
599
|
+
// If response is a timeout, we increase the number of timeouts so we can increase the timeout later.
|
|
603
600
|
if (response.isTimedOut) {
|
|
604
601
|
timeoutsCount++;
|
|
605
602
|
}
|
|
606
|
-
/**
|
|
607
|
-
* Failures are individually sent to the logger, allowing
|
|
608
|
-
* the end user to debug / store stack frames even
|
|
609
|
-
* when a retry error does not happen.
|
|
603
|
+
/**
|
|
604
|
+
* Failures are individually sent to the logger, allowing
|
|
605
|
+
* the end user to debug / store stack frames even
|
|
606
|
+
* when a retry error does not happen.
|
|
610
607
|
*/
|
|
611
608
|
// eslint-disable-next-line no-console -- this will be fixed by exposing a `logger` to the transporter
|
|
612
|
-
|
|
613
|
-
|
|
614
609
|
console.log('Retryable failure', stackFrameWithoutCredentials(stackFrame));
|
|
615
|
-
/**
|
|
616
|
-
* We also store the state of the host in failure cases. If the host, is
|
|
617
|
-
* down it will remain down for the next 2 minutes. In a timeout situation,
|
|
618
|
-
* this host will be added end of the list of hosts on the next request.
|
|
610
|
+
/**
|
|
611
|
+
* We also store the state of the host in failure cases. If the host, is
|
|
612
|
+
* down it will remain down for the next 2 minutes. In a timeout situation,
|
|
613
|
+
* this host will be added end of the list of hosts on the next request.
|
|
619
614
|
*/
|
|
620
|
-
|
|
621
615
|
await hostsCache.set(host, createStatefulHost(host, response.isTimedOut ? 'timed out' : 'down'));
|
|
622
616
|
return retry(retryableHosts, getTimeout);
|
|
623
617
|
}
|
|
624
|
-
|
|
625
618
|
if (isSuccess(response)) {
|
|
626
619
|
return deserializeSuccess(response);
|
|
627
620
|
}
|
|
628
|
-
|
|
629
621
|
pushToStackTrace(response);
|
|
630
622
|
throw deserializeFailure(response, stackTrace);
|
|
631
623
|
};
|
|
632
|
-
/**
|
|
633
|
-
* Finally, for each retryable host perform request until we got a non
|
|
634
|
-
* retryable response. Some notes here:
|
|
635
|
-
*
|
|
636
|
-
* 1. The reverse here is applied so we can apply a `pop` later on => more performant.
|
|
637
|
-
* 2. We also get from the retryable options a timeout multiplier that is tailored
|
|
638
|
-
* for the current context.
|
|
624
|
+
/**
|
|
625
|
+
* Finally, for each retryable host perform request until we got a non
|
|
626
|
+
* retryable response. Some notes here:
|
|
627
|
+
*
|
|
628
|
+
* 1. The reverse here is applied so we can apply a `pop` later on => more performant.
|
|
629
|
+
* 2. We also get from the retryable options a timeout multiplier that is tailored
|
|
630
|
+
* for the current context.
|
|
639
631
|
*/
|
|
640
|
-
|
|
641
|
-
|
|
642
632
|
const compatibleHosts = hosts.filter(host => host.accept === 'readWrite' || (isRead ? host.accept === 'read' : host.accept === 'write'));
|
|
643
633
|
const options = await createRetryableOptions(compatibleHosts);
|
|
644
634
|
return retry([...options.hosts].reverse(), options.getTimeout);
|
|
645
635
|
}
|
|
646
|
-
|
|
647
636
|
function createRequest(request, requestOptions = {}) {
|
|
648
|
-
/**
|
|
649
|
-
* A read request is either a `GET` request, or a request that we make
|
|
650
|
-
* via the `read` transporter (e.g. `search`).
|
|
637
|
+
/**
|
|
638
|
+
* A read request is either a `GET` request, or a request that we make
|
|
639
|
+
* via the `read` transporter (e.g. `search`).
|
|
651
640
|
*/
|
|
652
641
|
const isRead = request.useReadTransporter || request.method === 'GET';
|
|
653
|
-
|
|
654
642
|
if (!isRead) {
|
|
655
|
-
/**
|
|
656
|
-
* On write requests, no cache mechanisms are applied, and we
|
|
657
|
-
* proxy the request immediately to the requester.
|
|
643
|
+
/**
|
|
644
|
+
* On write requests, no cache mechanisms are applied, and we
|
|
645
|
+
* proxy the request immediately to the requester.
|
|
658
646
|
*/
|
|
659
647
|
return retryableRequest(request, requestOptions, isRead);
|
|
660
648
|
}
|
|
661
|
-
|
|
662
649
|
const createRetryableRequest = () => {
|
|
663
|
-
/**
|
|
664
|
-
* Then, we prepare a function factory that contains the construction of
|
|
665
|
-
* the retryable request. At this point, we may *not* perform the actual
|
|
666
|
-
* request. But we want to have the function factory ready.
|
|
650
|
+
/**
|
|
651
|
+
* Then, we prepare a function factory that contains the construction of
|
|
652
|
+
* the retryable request. At this point, we may *not* perform the actual
|
|
653
|
+
* request. But we want to have the function factory ready.
|
|
667
654
|
*/
|
|
668
655
|
return retryableRequest(request, requestOptions);
|
|
669
656
|
};
|
|
670
|
-
/**
|
|
671
|
-
* Once we have the function factory ready, we need to determine of the
|
|
672
|
-
* request is "cacheable" - should be cached. Note that, once again,
|
|
673
|
-
* the user can force this option.
|
|
657
|
+
/**
|
|
658
|
+
* Once we have the function factory ready, we need to determine of the
|
|
659
|
+
* request is "cacheable" - should be cached. Note that, once again,
|
|
660
|
+
* the user can force this option.
|
|
674
661
|
*/
|
|
675
|
-
|
|
676
|
-
|
|
677
662
|
const cacheable = requestOptions.cacheable || request.cacheable;
|
|
678
|
-
/**
|
|
679
|
-
* If is not "cacheable", we immediately trigger the retryable request, no
|
|
680
|
-
* need to check cache implementations.
|
|
663
|
+
/**
|
|
664
|
+
* If is not "cacheable", we immediately trigger the retryable request, no
|
|
665
|
+
* need to check cache implementations.
|
|
681
666
|
*/
|
|
682
|
-
|
|
683
667
|
if (cacheable !== true) {
|
|
684
668
|
return createRetryableRequest();
|
|
685
669
|
}
|
|
686
|
-
/**
|
|
687
|
-
* If the request is "cacheable", we need to first compute the key to ask
|
|
688
|
-
* the cache implementations if this request is on progress or if the
|
|
689
|
-
* response already exists on the cache.
|
|
670
|
+
/**
|
|
671
|
+
* If the request is "cacheable", we need to first compute the key to ask
|
|
672
|
+
* the cache implementations if this request is on progress or if the
|
|
673
|
+
* response already exists on the cache.
|
|
690
674
|
*/
|
|
691
|
-
|
|
692
|
-
|
|
693
675
|
const key = {
|
|
694
676
|
request,
|
|
695
677
|
requestOptions,
|
|
@@ -698,33 +680,31 @@ function createTransporter({
|
|
|
698
680
|
headers: baseHeaders
|
|
699
681
|
}
|
|
700
682
|
};
|
|
701
|
-
/**
|
|
702
|
-
* With the computed key, we first ask the responses cache
|
|
703
|
-
* implementation if this request was been resolved before.
|
|
683
|
+
/**
|
|
684
|
+
* With the computed key, we first ask the responses cache
|
|
685
|
+
* implementation if this request was been resolved before.
|
|
704
686
|
*/
|
|
705
|
-
|
|
706
687
|
return responsesCache.get(key, () => {
|
|
707
|
-
/**
|
|
708
|
-
* If the request has never resolved before, we actually ask if there
|
|
709
|
-
* is a current request with the same key on progress.
|
|
688
|
+
/**
|
|
689
|
+
* If the request has never resolved before, we actually ask if there
|
|
690
|
+
* is a current request with the same key on progress.
|
|
710
691
|
*/
|
|
711
692
|
return requestsCache.get(key, () =>
|
|
712
|
-
/**
|
|
713
|
-
* Finally, if there is no request in progress with the same key,
|
|
714
|
-
* this `createRetryableRequest()` will actually trigger the
|
|
715
|
-
* retryable request.
|
|
693
|
+
/**
|
|
694
|
+
* Finally, if there is no request in progress with the same key,
|
|
695
|
+
* this `createRetryableRequest()` will actually trigger the
|
|
696
|
+
* retryable request.
|
|
716
697
|
*/
|
|
717
698
|
requestsCache.set(key, createRetryableRequest()).then(response => Promise.all([requestsCache.delete(key), response]), err => Promise.all([requestsCache.delete(key), Promise.reject(err)])).then(([_, response]) => response));
|
|
718
699
|
}, {
|
|
719
|
-
/**
|
|
720
|
-
* Of course, once we get this response back from the server, we
|
|
721
|
-
* tell response cache to actually store the received response
|
|
722
|
-
* to be used later.
|
|
700
|
+
/**
|
|
701
|
+
* Of course, once we get this response back from the server, we
|
|
702
|
+
* tell response cache to actually store the received response
|
|
703
|
+
* to be used later.
|
|
723
704
|
*/
|
|
724
705
|
miss: response => responsesCache.set(key, response)
|
|
725
706
|
});
|
|
726
707
|
}
|
|
727
|
-
|
|
728
708
|
return {
|
|
729
709
|
hostsCache,
|
|
730
710
|
requester,
|
|
@@ -742,17 +722,13 @@ function createTransporter({
|
|
|
742
722
|
function createAlgoliaAgent(version) {
|
|
743
723
|
const algoliaAgent = {
|
|
744
724
|
value: `Algolia for JavaScript (${version})`,
|
|
745
|
-
|
|
746
725
|
add(options) {
|
|
747
726
|
const addedAlgoliaAgent = `; ${options.segment}${options.version !== undefined ? ` (${options.version})` : ''}`;
|
|
748
|
-
|
|
749
727
|
if (algoliaAgent.value.indexOf(addedAlgoliaAgent) === -1) {
|
|
750
728
|
algoliaAgent.value = `${algoliaAgent.value}${addedAlgoliaAgent}`;
|
|
751
729
|
}
|
|
752
|
-
|
|
753
730
|
return algoliaAgent;
|
|
754
731
|
}
|
|
755
|
-
|
|
756
732
|
};
|
|
757
733
|
return algoliaAgent;
|
|
758
734
|
}
|
|
@@ -786,6 +762,7 @@ exports.DEFAULT_READ_TIMEOUT_NODE = DEFAULT_READ_TIMEOUT_NODE;
|
|
|
786
762
|
exports.DEFAULT_WRITE_TIMEOUT_BROWSER = DEFAULT_WRITE_TIMEOUT_BROWSER;
|
|
787
763
|
exports.DEFAULT_WRITE_TIMEOUT_NODE = DEFAULT_WRITE_TIMEOUT_NODE;
|
|
788
764
|
exports.DeserializationError = DeserializationError;
|
|
765
|
+
exports.DetailedApiError = DetailedApiError;
|
|
789
766
|
exports.ErrorWithStackTrace = ErrorWithStackTrace;
|
|
790
767
|
exports.RetryError = RetryError;
|
|
791
768
|
exports.createAlgoliaAgent = createAlgoliaAgent;
|