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