UNPKG

85.5 kBJavaScriptView Raw
1function createBrowserLocalStorageCache(options) {
2 const namespaceKey = `algoliasearch-client-js-${options.key}`;
3 // eslint-disable-next-line functional/no-let
4 let storage;
5 const getStorage = () => {
6 if (storage === undefined) {
7 storage = options.localStorage || window.localStorage;
8 }
9 return storage;
10 };
11 const getNamespace = () => {
12 return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
13 };
14 const setNamespace = (namespace) => {
15 getStorage().setItem(namespaceKey, JSON.stringify(namespace));
16 };
17 const removeOutdatedCacheItems = () => {
18 const timeToLive = options.timeToLive ? options.timeToLive * 1000 : null;
19 const namespace = getNamespace();
20 const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries(Object.entries(namespace).filter(([, cacheItem]) => {
21 return cacheItem.timestamp !== undefined;
22 }));
23 setNamespace(filteredNamespaceWithoutOldFormattedCacheItems);
24 if (!timeToLive)
25 return;
26 const filteredNamespaceWithoutExpiredItems = Object.fromEntries(Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => {
27 const currentTimestamp = new Date().getTime();
28 const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;
29 return !isExpired;
30 }));
31 setNamespace(filteredNamespaceWithoutExpiredItems);
32 };
33 return {
34 get(key, defaultValue, events = {
35 miss: () => Promise.resolve(),
36 }) {
37 return Promise.resolve()
38 .then(() => {
39 removeOutdatedCacheItems();
40 const keyAsString = JSON.stringify(key);
41 return getNamespace()[keyAsString];
42 })
43 .then(value => {
44 return Promise.all([value ? value.value : defaultValue(), value !== undefined]);
45 })
46 .then(([value, exists]) => {
47 return Promise.all([value, exists || events.miss(value)]);
48 })
49 .then(([value]) => value);
50 },
51 set(key, value) {
52 return Promise.resolve().then(() => {
53 const namespace = getNamespace();
54 // eslint-disable-next-line functional/immutable-data
55 namespace[JSON.stringify(key)] = {
56 timestamp: new Date().getTime(),
57 value,
58 };
59 getStorage().setItem(namespaceKey, JSON.stringify(namespace));
60 return value;
61 });
62 },
63 delete(key) {
64 return Promise.resolve().then(() => {
65 const namespace = getNamespace();
66 // eslint-disable-next-line functional/immutable-data
67 delete namespace[JSON.stringify(key)];
68 getStorage().setItem(namespaceKey, JSON.stringify(namespace));
69 });
70 },
71 clear() {
72 return Promise.resolve().then(() => {
73 getStorage().removeItem(namespaceKey);
74 });
75 },
76 };
77}
78
79// @todo Add logger on options to debug when caches go wrong.
80function createFallbackableCache(options) {
81 const caches = [...options.caches];
82 const current = caches.shift(); // eslint-disable-line functional/immutable-data
83 if (current === undefined) {
84 return createNullCache();
85 }
86 return {
87 get(key, defaultValue, events = {
88 miss: () => Promise.resolve(),
89 }) {
90 return current.get(key, defaultValue, events).catch(() => {
91 return createFallbackableCache({ caches }).get(key, defaultValue, events);
92 });
93 },
94 set(key, value) {
95 return current.set(key, value).catch(() => {
96 return createFallbackableCache({ caches }).set(key, value);
97 });
98 },
99 delete(key) {
100 return current.delete(key).catch(() => {
101 return createFallbackableCache({ caches }).delete(key);
102 });
103 },
104 clear() {
105 return current.clear().catch(() => {
106 return createFallbackableCache({ caches }).clear();
107 });
108 },
109 };
110}
111
112function createNullCache() {
113 return {
114 get(_key, defaultValue, events = {
115 miss: () => Promise.resolve(),
116 }) {
117 const value = defaultValue();
118 return value
119 .then(result => Promise.all([result, events.miss(result)]))
120 .then(([result]) => result);
121 },
122 set(_key, value) {
123 return Promise.resolve(value);
124 },
125 delete(_key) {
126 return Promise.resolve();
127 },
128 clear() {
129 return Promise.resolve();
130 },
131 };
132}
133
134function createInMemoryCache(options = { serializable: true }) {
135 // eslint-disable-next-line functional/no-let
136 let cache = {};
137 return {
138 get(key, defaultValue, events = {
139 miss: () => Promise.resolve(),
140 }) {
141 const keyAsString = JSON.stringify(key);
142 if (keyAsString in cache) {
143 return Promise.resolve(options.serializable ? JSON.parse(cache[keyAsString]) : cache[keyAsString]);
144 }
145 const promise = defaultValue();
146 const miss = (events && events.miss) || (() => Promise.resolve());
147 return promise.then((value) => miss(value)).then(() => promise);
148 },
149 set(key, value) {
150 // eslint-disable-next-line functional/immutable-data
151 cache[JSON.stringify(key)] = options.serializable ? JSON.stringify(value) : value;
152 return Promise.resolve(value);
153 },
154 delete(key) {
155 // eslint-disable-next-line functional/immutable-data
156 delete cache[JSON.stringify(key)];
157 return Promise.resolve();
158 },
159 clear() {
160 cache = {};
161 return Promise.resolve();
162 },
163 };
164}
165
166function createAuth(authMode, appId, apiKey) {
167 const credentials = {
168 'x-algolia-api-key': apiKey,
169 'x-algolia-application-id': appId,
170 };
171 return {
172 headers() {
173 return authMode === AuthMode.WithinHeaders ? credentials : {};
174 },
175 queryParameters() {
176 return authMode === AuthMode.WithinQueryParameters ? credentials : {};
177 },
178 };
179}
180
181function createRetryablePromise(callback) {
182 let retriesCount = 0; // eslint-disable-line functional/no-let
183 const retry = () => {
184 retriesCount++;
185 return new Promise((resolve) => {
186 setTimeout(() => {
187 resolve(callback(retry));
188 }, Math.min(100 * retriesCount, 1000));
189 });
190 };
191 return callback(retry);
192}
193
194function createWaitablePromise(promise, wait = (_response, _requestOptions) => {
195 return Promise.resolve();
196}) {
197 // eslint-disable-next-line functional/immutable-data
198 return Object.assign(promise, {
199 wait(requestOptions) {
200 return createWaitablePromise(promise
201 .then(response => Promise.all([wait(response, requestOptions), response]))
202 .then(promiseResults => promiseResults[1]));
203 },
204 });
205}
206
207// eslint-disable-next-line functional/prefer-readonly-type
208function shuffle(array) {
209 let c = array.length - 1; // eslint-disable-line functional/no-let
210 // eslint-disable-next-line functional/no-loop-statement
211 for (c; c > 0; c--) {
212 const b = Math.floor(Math.random() * (c + 1));
213 const a = array[c];
214 array[c] = array[b]; // eslint-disable-line functional/immutable-data, no-param-reassign
215 array[b] = a; // eslint-disable-line functional/immutable-data, no-param-reassign
216 }
217 return array;
218}
219function addMethods(base, methods) {
220 if (!methods) {
221 return base;
222 }
223 Object.keys(methods).forEach(key => {
224 // eslint-disable-next-line functional/immutable-data, no-param-reassign
225 base[key] = methods[key](base);
226 });
227 return base;
228}
229function encode(format, ...args) {
230 // eslint-disable-next-line functional/no-let
231 let i = 0;
232 return format.replace(/%s/g, () => encodeURIComponent(args[i++]));
233}
234
235const version = '4.23.3';
236
237const AuthMode = {
238 /**
239 * If auth credentials should be in query parameters.
240 */
241 WithinQueryParameters: 0,
242 /**
243 * If auth credentials should be in headers.
244 */
245 WithinHeaders: 1,
246};
247
248function createMappedRequestOptions(requestOptions, timeout) {
249 const options = requestOptions || {};
250 const data = options.data || {};
251 Object.keys(options).forEach(key => {
252 if (['timeout', 'headers', 'queryParameters', 'data', 'cacheable'].indexOf(key) === -1) {
253 data[key] = options[key]; // eslint-disable-line functional/immutable-data
254 }
255 });
256 return {
257 data: Object.entries(data).length > 0 ? data : undefined,
258 timeout: options.timeout || timeout,
259 headers: options.headers || {},
260 queryParameters: options.queryParameters || {},
261 cacheable: options.cacheable,
262 };
263}
264
265const CallEnum = {
266 /**
267 * If the host is read only.
268 */
269 Read: 1,
270 /**
271 * If the host is write only.
272 */
273 Write: 2,
274 /**
275 * If the host is both read and write.
276 */
277 Any: 3,
278};
279
280const HostStatusEnum = {
281 Up: 1,
282 Down: 2,
283 Timeouted: 3,
284};
285
286// By default, API Clients at Algolia have expiration delay
287// of 5 mins. In the JavaScript client, we have 2 mins.
288const EXPIRATION_DELAY = 2 * 60 * 1000;
289function createStatefulHost(host, status = HostStatusEnum.Up) {
290 return {
291 ...host,
292 status,
293 lastUpdate: Date.now(),
294 };
295}
296function isStatefulHostUp(host) {
297 return host.status === HostStatusEnum.Up || Date.now() - host.lastUpdate > EXPIRATION_DELAY;
298}
299function isStatefulHostTimeouted(host) {
300 return (host.status === HostStatusEnum.Timeouted && Date.now() - host.lastUpdate <= EXPIRATION_DELAY);
301}
302
303function createStatelessHost(options) {
304 if (typeof options === 'string') {
305 return {
306 protocol: 'https',
307 url: options,
308 accept: CallEnum.Any,
309 };
310 }
311 return {
312 protocol: options.protocol || 'https',
313 url: options.url,
314 accept: options.accept || CallEnum.Any,
315 };
316}
317
318const MethodEnum = {
319 Delete: 'DELETE',
320 Get: 'GET',
321 Post: 'POST',
322 Put: 'PUT',
323};
324
325function createRetryableOptions(hostsCache, statelessHosts) {
326 return Promise.all(statelessHosts.map(statelessHost => {
327 return hostsCache.get(statelessHost, () => {
328 return Promise.resolve(createStatefulHost(statelessHost));
329 });
330 })).then(statefulHosts => {
331 const hostsUp = statefulHosts.filter(host => isStatefulHostUp(host));
332 const hostsTimeouted = statefulHosts.filter(host => isStatefulHostTimeouted(host));
333 /**
334 * Note, we put the hosts that previously timeouted on the end of the list.
335 */
336 const hostsAvailable = [...hostsUp, ...hostsTimeouted];
337 const statelessHostsAvailable = hostsAvailable.length > 0
338 ? hostsAvailable.map(host => createStatelessHost(host))
339 : statelessHosts;
340 return {
341 getTimeout(timeoutsCount, baseTimeout) {
342 /**
343 * Imagine that you have 4 hosts, if timeouts will increase
344 * on the following way: 1 (timeouted) > 4 (timeouted) > 5 (200)
345 *
346 * Note that, the very next request, we start from the previous timeout
347 *
348 * 5 (timeouted) > 6 (timeouted) > 7 ...
349 *
350 * This strategy may need to be reviewed, but is the strategy on the our
351 * current v3 version.
352 */
353 const timeoutMultiplier = hostsTimeouted.length === 0 && timeoutsCount === 0
354 ? 1
355 : hostsTimeouted.length + 3 + timeoutsCount;
356 return timeoutMultiplier * baseTimeout;
357 },
358 statelessHosts: statelessHostsAvailable,
359 };
360 });
361}
362
363const isNetworkError = ({ isTimedOut, status }) => {
364 return !isTimedOut && ~~status === 0;
365};
366const isRetryable = (response) => {
367 const status = response.status;
368 const isTimedOut = response.isTimedOut;
369 return (isTimedOut || isNetworkError(response) || (~~(status / 100) !== 2 && ~~(status / 100) !== 4));
370};
371const isSuccess = ({ status }) => {
372 return ~~(status / 100) === 2;
373};
374const retryDecision = (response, outcomes) => {
375 if (isRetryable(response)) {
376 return outcomes.onRetry(response);
377 }
378 if (isSuccess(response)) {
379 return outcomes.onSuccess(response);
380 }
381 return outcomes.onFail(response);
382};
383
384function retryableRequest(transporter, statelessHosts, request, requestOptions) {
385 const stackTrace = []; // eslint-disable-line functional/prefer-readonly-type
386 /**
387 * First we prepare the payload that do not depend from hosts.
388 */
389 const data = serializeData(request, requestOptions);
390 const headers = serializeHeaders(transporter, requestOptions);
391 const method = request.method;
392 // On `GET`, the data is proxied to query parameters.
393 const dataQueryParameters = request.method !== MethodEnum.Get
394 ? {}
395 : {
396 ...request.data,
397 ...requestOptions.data,
398 };
399 const queryParameters = {
400 'x-algolia-agent': transporter.userAgent.value,
401 ...transporter.queryParameters,
402 ...dataQueryParameters,
403 ...requestOptions.queryParameters,
404 };
405 let timeoutsCount = 0; // eslint-disable-line functional/no-let
406 const retry = (hosts, // eslint-disable-line functional/prefer-readonly-type
407 getTimeout) => {
408 /**
409 * We iterate on each host, until there is no host left.
410 */
411 const host = hosts.pop(); // eslint-disable-line functional/immutable-data
412 if (host === undefined) {
413 throw createRetryError(stackTraceWithoutCredentials(stackTrace));
414 }
415 const payload = {
416 data,
417 headers,
418 method,
419 url: serializeUrl(host, request.path, queryParameters),
420 connectTimeout: getTimeout(timeoutsCount, transporter.timeouts.connect),
421 responseTimeout: getTimeout(timeoutsCount, requestOptions.timeout),
422 };
423 /**
424 * The stackFrame is pushed to the stackTrace so we
425 * can have information about onRetry and onFailure
426 * decisions.
427 */
428 const pushToStackTrace = (response) => {
429 const stackFrame = {
430 request: payload,
431 response,
432 host,
433 triesLeft: hosts.length,
434 };
435 // eslint-disable-next-line functional/immutable-data
436 stackTrace.push(stackFrame);
437 return stackFrame;
438 };
439 const decisions = {
440 onSuccess: response => deserializeSuccess(response),
441 onRetry(response) {
442 const stackFrame = pushToStackTrace(response);
443 /**
444 * If response is a timeout, we increaset the number of
445 * timeouts so we can increase the timeout later.
446 */
447 if (response.isTimedOut) {
448 timeoutsCount++;
449 }
450 return Promise.all([
451 /**
452 * Failures are individually send the logger, allowing
453 * the end user to debug / store stack frames even
454 * when a retry error does not happen.
455 */
456 transporter.logger.info('Retryable failure', stackFrameWithoutCredentials(stackFrame)),
457 /**
458 * We also store the state of the host in failure cases. If the host, is
459 * down it will remain down for the next 2 minutes. In a timeout situation,
460 * this host will be added end of the list of hosts on the next request.
461 */
462 transporter.hostsCache.set(host, createStatefulHost(host, response.isTimedOut ? HostStatusEnum.Timeouted : HostStatusEnum.Down)),
463 ]).then(() => retry(hosts, getTimeout));
464 },
465 onFail(response) {
466 pushToStackTrace(response);
467 throw deserializeFailure(response, stackTraceWithoutCredentials(stackTrace));
468 },
469 };
470 return transporter.requester.send(payload).then(response => {
471 return retryDecision(response, decisions);
472 });
473 };
474 /**
475 * Finally, for each retryable host perform request until we got a non
476 * retryable response. Some notes here:
477 *
478 * 1. The reverse here is applied so we can apply a `pop` later on => more performant.
479 * 2. We also get from the retryable options a timeout multiplier that is tailored
480 * for the current context.
481 */
482 return createRetryableOptions(transporter.hostsCache, statelessHosts).then(options => {
483 return retry([...options.statelessHosts].reverse(), options.getTimeout);
484 });
485}
486
487function createTransporter(options) {
488 const { hostsCache, logger, requester, requestsCache, responsesCache, timeouts, userAgent, hosts, queryParameters, headers, } = options;
489 const transporter = {
490 hostsCache,
491 logger,
492 requester,
493 requestsCache,
494 responsesCache,
495 timeouts,
496 userAgent,
497 headers,
498 queryParameters,
499 hosts: hosts.map(host => createStatelessHost(host)),
500 read(request, requestOptions) {
501 /**
502 * First, we compute the user request options. Now, keep in mind,
503 * that using request options the user is able to modified the intire
504 * payload of the request. Such as headers, query parameters, and others.
505 */
506 const mappedRequestOptions = createMappedRequestOptions(requestOptions, transporter.timeouts.read);
507 const createRetryableRequest = () => {
508 /**
509 * Then, we prepare a function factory that contains the construction of
510 * the retryable request. At this point, we may *not* perform the actual
511 * request. But we want to have the function factory ready.
512 */
513 return retryableRequest(transporter, transporter.hosts.filter(host => (host.accept & CallEnum.Read) !== 0), request, mappedRequestOptions);
514 };
515 /**
516 * Once we have the function factory ready, we need to determine of the
517 * request is "cacheable" - should be cached. Note that, once again,
518 * the user can force this option.
519 */
520 const cacheable = mappedRequestOptions.cacheable !== undefined
521 ? mappedRequestOptions.cacheable
522 : request.cacheable;
523 /**
524 * If is not "cacheable", we immediatly trigger the retryable request, no
525 * need to check cache implementations.
526 */
527 if (cacheable !== true) {
528 return createRetryableRequest();
529 }
530 /**
531 * If the request is "cacheable", we need to first compute the key to ask
532 * the cache implementations if this request is on progress or if the
533 * response already exists on the cache.
534 */
535 const key = {
536 request,
537 mappedRequestOptions,
538 transporter: {
539 queryParameters: transporter.queryParameters,
540 headers: transporter.headers,
541 },
542 };
543 /**
544 * With the computed key, we first ask the responses cache
545 * implemention if this request was been resolved before.
546 */
547 return transporter.responsesCache.get(key, () => {
548 /**
549 * If the request has never resolved before, we actually ask if there
550 * is a current request with the same key on progress.
551 */
552 return transporter.requestsCache.get(key, () => {
553 return (transporter.requestsCache
554 /**
555 * Finally, if there is no request in progress with the same key,
556 * this `createRetryableRequest()` will actually trigger the
557 * retryable request.
558 */
559 .set(key, createRetryableRequest())
560 .then(response => Promise.all([transporter.requestsCache.delete(key), response]), err => Promise.all([transporter.requestsCache.delete(key), Promise.reject(err)]))
561 .then(([_, response]) => response));
562 });
563 }, {
564 /**
565 * Of course, once we get this response back from the server, we
566 * tell response cache to actually store the received response
567 * to be used later.
568 */
569 miss: response => transporter.responsesCache.set(key, response),
570 });
571 },
572 write(request, requestOptions) {
573 /**
574 * On write requests, no cache mechanisms are applied, and we
575 * proxy the request immediately to the requester.
576 */
577 return retryableRequest(transporter, transporter.hosts.filter(host => (host.accept & CallEnum.Write) !== 0), request, createMappedRequestOptions(requestOptions, transporter.timeouts.write));
578 },
579 };
580 return transporter;
581}
582
583function createUserAgent(version) {
584 const userAgent = {
585 value: `Algolia for JavaScript (${version})`,
586 add(options) {
587 const addedUserAgent = `; ${options.segment}${options.version !== undefined ? ` (${options.version})` : ''}`;
588 if (userAgent.value.indexOf(addedUserAgent) === -1) {
589 // eslint-disable-next-line functional/immutable-data
590 userAgent.value = `${userAgent.value}${addedUserAgent}`;
591 }
592 return userAgent;
593 },
594 };
595 return userAgent;
596}
597
598function deserializeSuccess(response) {
599 // eslint-disable-next-line functional/no-try-statement
600 try {
601 return JSON.parse(response.content);
602 }
603 catch (e) {
604 throw createDeserializationError(e.message, response);
605 }
606}
607function deserializeFailure({ content, status }, stackFrame) {
608 // eslint-disable-next-line functional/no-let
609 let message = content;
610 // eslint-disable-next-line functional/no-try-statement
611 try {
612 message = JSON.parse(content).message;
613 }
614 catch (e) {
615 // ..
616 }
617 return createApiError(message, status, stackFrame);
618}
619
620function serializeUrl(host, path, queryParameters) {
621 const queryParametersAsString = serializeQueryParameters(queryParameters);
622 // eslint-disable-next-line functional/no-let
623 let url = `${host.protocol}://${host.url}/${path.charAt(0) === '/' ? path.substr(1) : path}`;
624 if (queryParametersAsString.length) {
625 url += `?${queryParametersAsString}`;
626 }
627 return url;
628}
629function serializeQueryParameters(parameters) {
630 const isObjectOrArray = (value) => Object.prototype.toString.call(value) === '[object Object]' ||
631 Object.prototype.toString.call(value) === '[object Array]';
632 return Object.keys(parameters)
633 .map(key => encode('%s=%s', key, isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key]))
634 .join('&');
635}
636function serializeData(request, requestOptions) {
637 if (request.method === MethodEnum.Get ||
638 (request.data === undefined && requestOptions.data === undefined)) {
639 return undefined;
640 }
641 const data = Array.isArray(request.data)
642 ? request.data
643 : { ...request.data, ...requestOptions.data };
644 return JSON.stringify(data);
645}
646function serializeHeaders(transporter, requestOptions) {
647 const headers = {
648 ...transporter.headers,
649 ...requestOptions.headers,
650 };
651 const serializedHeaders = {};
652 Object.keys(headers).forEach(header => {
653 const value = headers[header];
654 // @ts-ignore
655 // eslint-disable-next-line functional/immutable-data
656 serializedHeaders[header.toLowerCase()] = value;
657 });
658 return serializedHeaders;
659}
660
661function stackTraceWithoutCredentials(stackTrace) {
662 return stackTrace.map(stackFrame => stackFrameWithoutCredentials(stackFrame));
663}
664function stackFrameWithoutCredentials(stackFrame) {
665 const modifiedHeaders = stackFrame.request.headers['x-algolia-api-key']
666 ? { 'x-algolia-api-key': '*****' }
667 : {};
668 return {
669 ...stackFrame,
670 request: {
671 ...stackFrame.request,
672 headers: {
673 ...stackFrame.request.headers,
674 ...modifiedHeaders,
675 },
676 },
677 };
678}
679
680function createApiError(message, status, transporterStackTrace) {
681 return {
682 name: 'ApiError',
683 message,
684 status,
685 transporterStackTrace,
686 };
687}
688
689function createDeserializationError(message, response) {
690 return {
691 name: 'DeserializationError',
692 message,
693 response,
694 };
695}
696
697function createRetryError(transporterStackTrace) {
698 return {
699 name: 'RetryError',
700 message: 'Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.',
701 transporterStackTrace,
702 };
703}
704
705const createAnalyticsClient = options => {
706 const region = options.region || 'us';
707 const auth = createAuth(AuthMode.WithinHeaders, options.appId, options.apiKey);
708 const transporter = createTransporter({
709 hosts: [{ url: `analytics.${region}.algolia.com` }],
710 ...options,
711 headers: {
712 ...auth.headers(),
713 ...{ 'content-type': 'application/json' },
714 ...options.headers,
715 },
716 queryParameters: {
717 ...auth.queryParameters(),
718 ...options.queryParameters,
719 },
720 });
721 const appId = options.appId;
722 return addMethods({ appId, transporter }, options.methods);
723};
724
725const addABTest = (base) => {
726 return (abTest, requestOptions) => {
727 return base.transporter.write({
728 method: MethodEnum.Post,
729 path: '2/abtests',
730 data: abTest,
731 }, requestOptions);
732 };
733};
734
735const deleteABTest = (base) => {
736 return (abTestID, requestOptions) => {
737 return base.transporter.write({
738 method: MethodEnum.Delete,
739 path: encode('2/abtests/%s', abTestID),
740 }, requestOptions);
741 };
742};
743
744const getABTest = (base) => {
745 return (abTestID, requestOptions) => {
746 return base.transporter.read({
747 method: MethodEnum.Get,
748 path: encode('2/abtests/%s', abTestID),
749 }, requestOptions);
750 };
751};
752
753const getABTests = (base) => {
754 return (requestOptions) => {
755 return base.transporter.read({
756 method: MethodEnum.Get,
757 path: '2/abtests',
758 }, requestOptions);
759 };
760};
761
762const stopABTest = (base) => {
763 return (abTestID, requestOptions) => {
764 return base.transporter.write({
765 method: MethodEnum.Post,
766 path: encode('2/abtests/%s/stop', abTestID),
767 }, requestOptions);
768 };
769};
770
771const createPersonalizationClient = options => {
772 const region = options.region || 'us';
773 const auth = createAuth(AuthMode.WithinHeaders, options.appId, options.apiKey);
774 const transporter = createTransporter({
775 hosts: [{ url: `personalization.${region}.algolia.com` }],
776 ...options,
777 headers: {
778 ...auth.headers(),
779 ...{ 'content-type': 'application/json' },
780 ...options.headers,
781 },
782 queryParameters: {
783 ...auth.queryParameters(),
784 ...options.queryParameters,
785 },
786 });
787 return addMethods({ appId: options.appId, transporter }, options.methods);
788};
789
790const getPersonalizationStrategy = (base) => {
791 return (requestOptions) => {
792 return base.transporter.read({
793 method: MethodEnum.Get,
794 path: '1/strategies/personalization',
795 }, requestOptions);
796 };
797};
798
799const setPersonalizationStrategy = (base) => {
800 return (personalizationStrategy, requestOptions) => {
801 return base.transporter.write({
802 method: MethodEnum.Post,
803 path: '1/strategies/personalization',
804 data: personalizationStrategy,
805 }, requestOptions);
806 };
807};
808
809function createBrowsablePromise(options) {
810 const browse = (data) => {
811 return options.request(data).then(response => {
812 /**
813 * First we send to the developer the
814 * batch retrieved from the API.
815 */
816 if (options.batch !== undefined) {
817 options.batch(response.hits);
818 }
819 /**
820 * Then, we ask to the browse concrete implementation
821 * if we should stop browsing. As example, the `browseObjects`
822 * method will stop if the cursor is not present on the response.
823 */
824 if (options.shouldStop(response)) {
825 return undefined;
826 }
827 /**
828 * Finally, if the response contains a cursor, we browse to the next
829 * batch using that same cursor. Otherwise, we just use the traditional
830 * browsing using the page element.
831 */
832 if (response.cursor) {
833 return browse({
834 cursor: response.cursor,
835 });
836 }
837 return browse({
838 page: (data.page || 0) + 1,
839 });
840 });
841 };
842 return browse({});
843}
844
845const createSearchClient = options => {
846 const appId = options.appId;
847 const auth = createAuth(options.authMode !== undefined ? options.authMode : AuthMode.WithinHeaders, appId, options.apiKey);
848 const transporter = createTransporter({
849 hosts: [
850 { url: `${appId}-dsn.algolia.net`, accept: CallEnum.Read },
851 { url: `${appId}.algolia.net`, accept: CallEnum.Write },
852 ].concat(shuffle([
853 { url: `${appId}-1.algolianet.com` },
854 { url: `${appId}-2.algolianet.com` },
855 { url: `${appId}-3.algolianet.com` },
856 ])),
857 ...options,
858 headers: {
859 ...auth.headers(),
860 ...{ 'content-type': 'application/x-www-form-urlencoded' },
861 ...options.headers,
862 },
863 queryParameters: {
864 ...auth.queryParameters(),
865 ...options.queryParameters,
866 },
867 });
868 const base = {
869 transporter,
870 appId,
871 addAlgoliaAgent(segment, version) {
872 transporter.userAgent.add({ segment, version });
873 },
874 clearCache() {
875 return Promise.all([
876 transporter.requestsCache.clear(),
877 transporter.responsesCache.clear(),
878 ]).then(() => undefined);
879 },
880 };
881 return addMethods(base, options.methods);
882};
883
884function createMissingObjectIDError() {
885 return {
886 name: 'MissingObjectIDError',
887 message: 'All objects must have an unique objectID ' +
888 '(like a primary key) to be valid. ' +
889 'Algolia is also able to generate objectIDs ' +
890 "automatically but *it's not recommended*. " +
891 "To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option.",
892 };
893}
894
895function createObjectNotFoundError() {
896 return {
897 name: 'ObjectNotFoundError',
898 message: 'Object not found.',
899 };
900}
901
902const addApiKey = (base) => {
903 return (acl, requestOptions) => {
904 const { queryParameters, ...options } = requestOptions || {};
905 const data = {
906 acl,
907 ...(queryParameters !== undefined ? { queryParameters } : {}),
908 };
909 const wait = (response, waitRequestOptions) => {
910 return createRetryablePromise(retry => {
911 return getApiKey(base)(response.key, waitRequestOptions).catch((apiError) => {
912 if (apiError.status !== 404) {
913 throw apiError;
914 }
915 return retry();
916 });
917 });
918 };
919 return createWaitablePromise(base.transporter.write({
920 method: MethodEnum.Post,
921 path: '1/keys',
922 data,
923 }, options), wait);
924 };
925};
926
927const assignUserID = (base) => {
928 return (userID, clusterName, requestOptions) => {
929 const mappedRequestOptions = createMappedRequestOptions(requestOptions);
930 // eslint-disable-next-line functional/immutable-data
931 mappedRequestOptions.queryParameters['X-Algolia-User-ID'] = userID;
932 return base.transporter.write({
933 method: MethodEnum.Post,
934 path: '1/clusters/mapping',
935 data: { cluster: clusterName },
936 }, mappedRequestOptions);
937 };
938};
939
940const assignUserIDs = (base) => {
941 return (userIDs, clusterName, requestOptions) => {
942 return base.transporter.write({
943 method: MethodEnum.Post,
944 path: '1/clusters/mapping/batch',
945 data: {
946 users: userIDs,
947 cluster: clusterName,
948 },
949 }, requestOptions);
950 };
951};
952
953const clearDictionaryEntries = (base) => {
954 return (dictionary, requestOptions) => {
955 return createWaitablePromise(base.transporter.write({
956 method: MethodEnum.Post,
957 path: encode('/1/dictionaries/%s/batch', dictionary),
958 data: {
959 clearExistingDictionaryEntries: true,
960 requests: { action: 'addEntry', body: [] },
961 },
962 }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
963 };
964};
965
966const copyIndex = (base) => {
967 return (from, to, requestOptions) => {
968 const wait = (response, waitRequestOptions) => {
969 return initIndex(base)(from, {
970 methods: { waitTask },
971 }).waitTask(response.taskID, waitRequestOptions);
972 };
973 return createWaitablePromise(base.transporter.write({
974 method: MethodEnum.Post,
975 path: encode('1/indexes/%s/operation', from),
976 data: {
977 operation: 'copy',
978 destination: to,
979 },
980 }, requestOptions), wait);
981 };
982};
983
984const copyRules = (base) => {
985 return (from, to, requestOptions) => {
986 return copyIndex(base)(from, to, {
987 ...requestOptions,
988 scope: [ScopeEnum.Rules],
989 });
990 };
991};
992
993const copySettings = (base) => {
994 return (from, to, requestOptions) => {
995 return copyIndex(base)(from, to, {
996 ...requestOptions,
997 scope: [ScopeEnum.Settings],
998 });
999 };
1000};
1001
1002const copySynonyms = (base) => {
1003 return (from, to, requestOptions) => {
1004 return copyIndex(base)(from, to, {
1005 ...requestOptions,
1006 scope: [ScopeEnum.Synonyms],
1007 });
1008 };
1009};
1010
1011const customRequest = (base) => {
1012 return (request, requestOptions) => {
1013 if (request.method === MethodEnum.Get) {
1014 return base.transporter.read(request, requestOptions);
1015 }
1016 return base.transporter.write(request, requestOptions);
1017 };
1018};
1019
1020const deleteApiKey = (base) => {
1021 return (apiKey, requestOptions) => {
1022 const wait = (_, waitRequestOptions) => {
1023 return createRetryablePromise(retry => {
1024 return getApiKey(base)(apiKey, waitRequestOptions)
1025 .then(retry)
1026 .catch((apiError) => {
1027 if (apiError.status !== 404) {
1028 throw apiError;
1029 }
1030 });
1031 });
1032 };
1033 return createWaitablePromise(base.transporter.write({
1034 method: MethodEnum.Delete,
1035 path: encode('1/keys/%s', apiKey),
1036 }, requestOptions), wait);
1037 };
1038};
1039
1040const deleteDictionaryEntries = (base) => {
1041 return (dictionary, objectIDs, requestOptions) => {
1042 const requests = objectIDs.map(objectID => ({
1043 action: 'deleteEntry',
1044 body: { objectID },
1045 }));
1046 return createWaitablePromise(base.transporter.write({
1047 method: MethodEnum.Post,
1048 path: encode('/1/dictionaries/%s/batch', dictionary),
1049 data: { clearExistingDictionaryEntries: false, requests },
1050 }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
1051 };
1052};
1053
1054const getApiKey = (base) => {
1055 return (apiKey, requestOptions) => {
1056 return base.transporter.read({
1057 method: MethodEnum.Get,
1058 path: encode('1/keys/%s', apiKey),
1059 }, requestOptions);
1060 };
1061};
1062
1063const getAppTask = (base) => {
1064 return (taskID, requestOptions) => {
1065 return base.transporter.read({
1066 method: MethodEnum.Get,
1067 path: encode('1/task/%s', taskID.toString()),
1068 }, requestOptions);
1069 };
1070};
1071
1072const getDictionarySettings = (base) => {
1073 return (requestOptions) => {
1074 return base.transporter.read({
1075 method: MethodEnum.Get,
1076 path: '/1/dictionaries/*/settings',
1077 }, requestOptions);
1078 };
1079};
1080
1081const getLogs = (base) => {
1082 return (requestOptions) => {
1083 return base.transporter.read({
1084 method: MethodEnum.Get,
1085 path: '1/logs',
1086 }, requestOptions);
1087 };
1088};
1089
1090const getTopUserIDs = (base) => {
1091 return (requestOptions) => {
1092 return base.transporter.read({
1093 method: MethodEnum.Get,
1094 path: '1/clusters/mapping/top',
1095 }, requestOptions);
1096 };
1097};
1098
1099const getUserID = (base) => {
1100 return (userID, requestOptions) => {
1101 return base.transporter.read({
1102 method: MethodEnum.Get,
1103 path: encode('1/clusters/mapping/%s', userID),
1104 }, requestOptions);
1105 };
1106};
1107
1108const hasPendingMappings = (base) => {
1109 return (requestOptions) => {
1110 const { retrieveMappings, ...options } = requestOptions || {};
1111 if (retrieveMappings === true) {
1112 // eslint-disable-next-line functional/immutable-data
1113 options.getClusters = true;
1114 }
1115 return base.transporter.read({
1116 method: MethodEnum.Get,
1117 path: '1/clusters/mapping/pending',
1118 }, options);
1119 };
1120};
1121
1122const initIndex = (base) => {
1123 return (indexName, options = {}) => {
1124 const searchIndex = {
1125 transporter: base.transporter,
1126 appId: base.appId,
1127 indexName,
1128 };
1129 return addMethods(searchIndex, options.methods);
1130 };
1131};
1132
1133const listApiKeys = (base) => {
1134 return (requestOptions) => {
1135 return base.transporter.read({
1136 method: MethodEnum.Get,
1137 path: '1/keys',
1138 }, requestOptions);
1139 };
1140};
1141
1142const listClusters = (base) => {
1143 return (requestOptions) => {
1144 return base.transporter.read({
1145 method: MethodEnum.Get,
1146 path: '1/clusters',
1147 }, requestOptions);
1148 };
1149};
1150
1151const listIndices = (base) => {
1152 return (requestOptions) => {
1153 return base.transporter.read({
1154 method: MethodEnum.Get,
1155 path: '1/indexes',
1156 }, requestOptions);
1157 };
1158};
1159
1160const listUserIDs = (base) => {
1161 return (requestOptions) => {
1162 return base.transporter.read({
1163 method: MethodEnum.Get,
1164 path: '1/clusters/mapping',
1165 }, requestOptions);
1166 };
1167};
1168
1169const moveIndex = (base) => {
1170 return (from, to, requestOptions) => {
1171 const wait = (response, waitRequestOptions) => {
1172 return initIndex(base)(from, {
1173 methods: { waitTask },
1174 }).waitTask(response.taskID, waitRequestOptions);
1175 };
1176 return createWaitablePromise(base.transporter.write({
1177 method: MethodEnum.Post,
1178 path: encode('1/indexes/%s/operation', from),
1179 data: {
1180 operation: 'move',
1181 destination: to,
1182 },
1183 }, requestOptions), wait);
1184 };
1185};
1186
1187const multipleBatch = (base) => {
1188 return (requests, requestOptions) => {
1189 const wait = (response, waitRequestOptions) => {
1190 return Promise.all(Object.keys(response.taskID).map(indexName => {
1191 return initIndex(base)(indexName, {
1192 methods: { waitTask },
1193 }).waitTask(response.taskID[indexName], waitRequestOptions);
1194 }));
1195 };
1196 return createWaitablePromise(base.transporter.write({
1197 method: MethodEnum.Post,
1198 path: '1/indexes/*/batch',
1199 data: {
1200 requests,
1201 },
1202 }, requestOptions), wait);
1203 };
1204};
1205
1206const multipleGetObjects = (base) => {
1207 return (requests, requestOptions) => {
1208 return base.transporter.read({
1209 method: MethodEnum.Post,
1210 path: '1/indexes/*/objects',
1211 data: {
1212 requests,
1213 },
1214 }, requestOptions);
1215 };
1216};
1217
1218const multipleQueries = (base) => {
1219 return (queries, requestOptions) => {
1220 const requests = queries.map(query => {
1221 return {
1222 ...query,
1223 params: serializeQueryParameters(query.params || {}),
1224 };
1225 });
1226 return base.transporter.read({
1227 method: MethodEnum.Post,
1228 path: '1/indexes/*/queries',
1229 data: {
1230 requests,
1231 },
1232 cacheable: true,
1233 }, requestOptions);
1234 };
1235};
1236
1237const multipleSearchForFacetValues = (base) => {
1238 return (queries, requestOptions) => {
1239 return Promise.all(queries.map(query => {
1240 const { facetName, facetQuery, ...params } = query.params;
1241 return initIndex(base)(query.indexName, {
1242 methods: { searchForFacetValues },
1243 }).searchForFacetValues(facetName, facetQuery, {
1244 ...requestOptions,
1245 ...params,
1246 });
1247 }));
1248 };
1249};
1250
1251const removeUserID = (base) => {
1252 return (userID, requestOptions) => {
1253 const mappedRequestOptions = createMappedRequestOptions(requestOptions);
1254 // eslint-disable-next-line functional/immutable-data
1255 mappedRequestOptions.queryParameters['X-Algolia-User-ID'] = userID;
1256 return base.transporter.write({
1257 method: MethodEnum.Delete,
1258 path: '1/clusters/mapping',
1259 }, mappedRequestOptions);
1260 };
1261};
1262
1263const replaceDictionaryEntries = (base) => {
1264 return (dictionary, entries, requestOptions) => {
1265 const requests = entries.map(entry => ({
1266 action: 'addEntry',
1267 body: entry,
1268 }));
1269 return createWaitablePromise(base.transporter.write({
1270 method: MethodEnum.Post,
1271 path: encode('/1/dictionaries/%s/batch', dictionary),
1272 data: { clearExistingDictionaryEntries: true, requests },
1273 }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
1274 };
1275};
1276
1277const restoreApiKey = (base) => {
1278 return (apiKey, requestOptions) => {
1279 const wait = (_, waitRequestOptions) => {
1280 return createRetryablePromise(retry => {
1281 return getApiKey(base)(apiKey, waitRequestOptions).catch((apiError) => {
1282 if (apiError.status !== 404) {
1283 throw apiError;
1284 }
1285 return retry();
1286 });
1287 });
1288 };
1289 return createWaitablePromise(base.transporter.write({
1290 method: MethodEnum.Post,
1291 path: encode('1/keys/%s/restore', apiKey),
1292 }, requestOptions), wait);
1293 };
1294};
1295
1296const saveDictionaryEntries = (base) => {
1297 return (dictionary, entries, requestOptions) => {
1298 const requests = entries.map(entry => ({
1299 action: 'addEntry',
1300 body: entry,
1301 }));
1302 return createWaitablePromise(base.transporter.write({
1303 method: MethodEnum.Post,
1304 path: encode('/1/dictionaries/%s/batch', dictionary),
1305 data: { clearExistingDictionaryEntries: false, requests },
1306 }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
1307 };
1308};
1309
1310const searchDictionaryEntries = (base) => {
1311 return (dictionary, query, requestOptions) => {
1312 return base.transporter.read({
1313 method: MethodEnum.Post,
1314 path: encode('/1/dictionaries/%s/search', dictionary),
1315 data: {
1316 query,
1317 },
1318 cacheable: true,
1319 }, requestOptions);
1320 };
1321};
1322
1323const searchUserIDs = (base) => {
1324 return (query, requestOptions) => {
1325 return base.transporter.read({
1326 method: MethodEnum.Post,
1327 path: '1/clusters/mapping/search',
1328 data: {
1329 query,
1330 },
1331 }, requestOptions);
1332 };
1333};
1334
1335const setDictionarySettings = (base) => {
1336 return (settings, requestOptions) => {
1337 return createWaitablePromise(base.transporter.write({
1338 method: MethodEnum.Put,
1339 path: '/1/dictionaries/*/settings',
1340 data: settings,
1341 }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
1342 };
1343};
1344
1345const updateApiKey = (base) => {
1346 return (apiKey, requestOptions) => {
1347 const updatedFields = Object.assign({}, requestOptions);
1348 const { queryParameters, ...options } = requestOptions || {};
1349 const data = queryParameters ? { queryParameters } : {};
1350 const apiKeyFields = [
1351 'acl',
1352 'indexes',
1353 'referers',
1354 'restrictSources',
1355 'queryParameters',
1356 'description',
1357 'maxQueriesPerIPPerHour',
1358 'maxHitsPerQuery',
1359 ];
1360 // Check that all the fields retrieved through getApiKey are the same as the ones we wanted to update
1361 const hasChanged = (getApiKeyResponse) => {
1362 return Object.keys(updatedFields)
1363 .filter((updatedField) => apiKeyFields.indexOf(updatedField) !== -1)
1364 .every(updatedField => {
1365 // If the field is an array, we need to check that they are the same length and that all the values are the same
1366 if (Array.isArray(getApiKeyResponse[updatedField]) &&
1367 Array.isArray(updatedFields[updatedField])) {
1368 const getApiKeyResponseArray = getApiKeyResponse[updatedField];
1369 return (getApiKeyResponseArray.length === updatedFields[updatedField].length &&
1370 getApiKeyResponseArray.every((value, index) => value === updatedFields[updatedField][index]));
1371 }
1372 else {
1373 return getApiKeyResponse[updatedField] === updatedFields[updatedField];
1374 }
1375 });
1376 };
1377 const wait = (_, waitRequestOptions) => createRetryablePromise(retry => {
1378 return getApiKey(base)(apiKey, waitRequestOptions).then(getApiKeyResponse => {
1379 return hasChanged(getApiKeyResponse) ? Promise.resolve() : retry();
1380 });
1381 });
1382 return createWaitablePromise(base.transporter.write({
1383 method: MethodEnum.Put,
1384 path: encode('1/keys/%s', apiKey),
1385 data,
1386 }, options), wait);
1387 };
1388};
1389
1390const waitAppTask = (base) => {
1391 return (taskID, requestOptions) => {
1392 return createRetryablePromise(retry => {
1393 return getAppTask(base)(taskID, requestOptions).then(response => {
1394 return response.status !== 'published' ? retry() : undefined;
1395 });
1396 });
1397 };
1398};
1399
1400const batch = (base) => {
1401 return (requests, requestOptions) => {
1402 const wait = (response, waitRequestOptions) => {
1403 return waitTask(base)(response.taskID, waitRequestOptions);
1404 };
1405 return createWaitablePromise(base.transporter.write({
1406 method: MethodEnum.Post,
1407 path: encode('1/indexes/%s/batch', base.indexName),
1408 data: {
1409 requests,
1410 },
1411 }, requestOptions), wait);
1412 };
1413};
1414
1415const browseObjects = (base) => {
1416 return (requestOptions) => {
1417 return createBrowsablePromise({
1418 shouldStop: response => response.cursor === undefined,
1419 ...requestOptions,
1420 request: (data) => base.transporter.read({
1421 method: MethodEnum.Post,
1422 path: encode('1/indexes/%s/browse', base.indexName),
1423 data,
1424 }, requestOptions),
1425 });
1426 };
1427};
1428
1429const browseRules = (base) => {
1430 return (requestOptions) => {
1431 const options = {
1432 hitsPerPage: 1000,
1433 ...requestOptions,
1434 };
1435 return createBrowsablePromise({
1436 shouldStop: response => response.hits.length < options.hitsPerPage,
1437 ...options,
1438 request(data) {
1439 return searchRules(base)('', { ...options, ...data }).then((response) => {
1440 return {
1441 ...response,
1442 hits: response.hits.map(rule => {
1443 // eslint-disable-next-line functional/immutable-data,no-param-reassign
1444 delete rule._highlightResult;
1445 return rule;
1446 }),
1447 };
1448 });
1449 },
1450 });
1451 };
1452};
1453
1454const browseSynonyms = (base) => {
1455 return (requestOptions) => {
1456 const options = {
1457 hitsPerPage: 1000,
1458 ...requestOptions,
1459 };
1460 return createBrowsablePromise({
1461 shouldStop: response => response.hits.length < options.hitsPerPage,
1462 ...options,
1463 request(data) {
1464 return searchSynonyms(base)('', { ...options, ...data }).then((response) => {
1465 return {
1466 ...response,
1467 hits: response.hits.map(synonym => {
1468 // eslint-disable-next-line functional/immutable-data,no-param-reassign
1469 delete synonym._highlightResult;
1470 return synonym;
1471 }),
1472 };
1473 });
1474 },
1475 });
1476 };
1477};
1478
1479const chunkedBatch = (base) => {
1480 return (bodies, action, requestOptions) => {
1481 const { batchSize, ...options } = requestOptions || {};
1482 const response = {
1483 taskIDs: [],
1484 objectIDs: [],
1485 };
1486 const forEachBatch = (lastIndex = 0) => {
1487 // eslint-disable-next-line functional/prefer-readonly-type
1488 const bodiesChunk = [];
1489 // eslint-disable-next-line functional/no-let
1490 let index;
1491 /* eslint-disable-next-line functional/no-loop-statement */
1492 for (index = lastIndex; index < bodies.length; index++) {
1493 // eslint-disable-next-line functional/immutable-data
1494 bodiesChunk.push(bodies[index]);
1495 if (bodiesChunk.length === (batchSize || 1000)) {
1496 break;
1497 }
1498 }
1499 if (bodiesChunk.length === 0) {
1500 return Promise.resolve(response);
1501 }
1502 return batch(base)(bodiesChunk.map(body => {
1503 return {
1504 action,
1505 body,
1506 };
1507 }), options).then(res => {
1508 response.objectIDs = response.objectIDs.concat(res.objectIDs); // eslint-disable-line functional/immutable-data
1509 response.taskIDs.push(res.taskID); // eslint-disable-line functional/immutable-data
1510 index++;
1511 return forEachBatch(index);
1512 });
1513 };
1514 return createWaitablePromise(forEachBatch(), (chunkedBatchResponse, waitRequestOptions) => {
1515 return Promise.all(chunkedBatchResponse.taskIDs.map(taskID => {
1516 return waitTask(base)(taskID, waitRequestOptions);
1517 }));
1518 });
1519 };
1520};
1521
1522const clearObjects = (base) => {
1523 return (requestOptions) => {
1524 return createWaitablePromise(base.transporter.write({
1525 method: MethodEnum.Post,
1526 path: encode('1/indexes/%s/clear', base.indexName),
1527 }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1528 };
1529};
1530
1531const clearRules = (base) => {
1532 return (requestOptions) => {
1533 const { forwardToReplicas, ...options } = requestOptions || {};
1534 const mappedRequestOptions = createMappedRequestOptions(options);
1535 if (forwardToReplicas) {
1536 mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
1537 }
1538 return createWaitablePromise(base.transporter.write({
1539 method: MethodEnum.Post,
1540 path: encode('1/indexes/%s/rules/clear', base.indexName),
1541 }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1542 };
1543};
1544
1545const clearSynonyms = (base) => {
1546 return (requestOptions) => {
1547 const { forwardToReplicas, ...options } = requestOptions || {};
1548 const mappedRequestOptions = createMappedRequestOptions(options);
1549 if (forwardToReplicas) {
1550 mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
1551 }
1552 return createWaitablePromise(base.transporter.write({
1553 method: MethodEnum.Post,
1554 path: encode('1/indexes/%s/synonyms/clear', base.indexName),
1555 }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1556 };
1557};
1558
1559const deleteBy = (base) => {
1560 return (filters, requestOptions) => {
1561 return createWaitablePromise(base.transporter.write({
1562 method: MethodEnum.Post,
1563 path: encode('1/indexes/%s/deleteByQuery', base.indexName),
1564 data: filters,
1565 }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1566 };
1567};
1568
1569const deleteIndex = (base) => {
1570 return (requestOptions) => {
1571 return createWaitablePromise(base.transporter.write({
1572 method: MethodEnum.Delete,
1573 path: encode('1/indexes/%s', base.indexName),
1574 }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1575 };
1576};
1577
1578const deleteObject = (base) => {
1579 return (objectID, requestOptions) => {
1580 return createWaitablePromise(deleteObjects(base)([objectID], requestOptions).then(response => {
1581 return { taskID: response.taskIDs[0] };
1582 }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1583 };
1584};
1585
1586const deleteObjects = (base) => {
1587 return (objectIDs, requestOptions) => {
1588 const objects = objectIDs.map(objectID => {
1589 return { objectID };
1590 });
1591 return chunkedBatch(base)(objects, BatchActionEnum.DeleteObject, requestOptions);
1592 };
1593};
1594
1595const deleteRule = (base) => {
1596 return (objectID, requestOptions) => {
1597 const { forwardToReplicas, ...options } = requestOptions || {};
1598 const mappedRequestOptions = createMappedRequestOptions(options);
1599 if (forwardToReplicas) {
1600 mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
1601 }
1602 return createWaitablePromise(base.transporter.write({
1603 method: MethodEnum.Delete,
1604 path: encode('1/indexes/%s/rules/%s', base.indexName, objectID),
1605 }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1606 };
1607};
1608
1609const deleteSynonym = (base) => {
1610 return (objectID, requestOptions) => {
1611 const { forwardToReplicas, ...options } = requestOptions || {};
1612 const mappedRequestOptions = createMappedRequestOptions(options);
1613 if (forwardToReplicas) {
1614 mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
1615 }
1616 return createWaitablePromise(base.transporter.write({
1617 method: MethodEnum.Delete,
1618 path: encode('1/indexes/%s/synonyms/%s', base.indexName, objectID),
1619 }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1620 };
1621};
1622
1623const exists = (base) => {
1624 return (requestOptions) => {
1625 return getSettings(base)(requestOptions)
1626 .then(() => true)
1627 .catch(error => {
1628 if (error.status !== 404) {
1629 throw error;
1630 }
1631 return false;
1632 });
1633 };
1634};
1635
1636const findAnswers = (base) => {
1637 return (query, queryLanguages, requestOptions) => {
1638 return base.transporter.read({
1639 method: MethodEnum.Post,
1640 path: encode('1/answers/%s/prediction', base.indexName),
1641 data: {
1642 query,
1643 queryLanguages,
1644 },
1645 cacheable: true,
1646 }, requestOptions);
1647 };
1648};
1649
1650const findObject = (base) => {
1651 return (callback, requestOptions) => {
1652 const { query, paginate, ...options } = requestOptions || {};
1653 // eslint-disable-next-line functional/no-let
1654 let page = 0;
1655 const forEachPage = () => {
1656 return search(base)(query || '', { ...options, page }).then(result => {
1657 // eslint-disable-next-line functional/no-loop-statement
1658 for (const [position, hit] of Object.entries(result.hits)) {
1659 // eslint-disable-next-line promise/no-callback-in-promise
1660 if (callback(hit)) {
1661 return {
1662 object: hit,
1663 position: parseInt(position, 10),
1664 page,
1665 };
1666 }
1667 }
1668 page++;
1669 // paginate if option was set and has next page
1670 if (paginate === false || page >= result.nbPages) {
1671 throw createObjectNotFoundError();
1672 }
1673 return forEachPage();
1674 });
1675 };
1676 return forEachPage();
1677 };
1678};
1679
1680const getObject = (base) => {
1681 return (objectID, requestOptions) => {
1682 return base.transporter.read({
1683 method: MethodEnum.Get,
1684 path: encode('1/indexes/%s/%s', base.indexName, objectID),
1685 }, requestOptions);
1686 };
1687};
1688
1689const getObjectPosition = () => {
1690 return (searchResponse, objectID) => {
1691 // eslint-disable-next-line functional/no-loop-statement
1692 for (const [position, hit] of Object.entries(searchResponse.hits)) {
1693 if (hit.objectID === objectID) {
1694 return parseInt(position, 10);
1695 }
1696 }
1697 return -1;
1698 };
1699};
1700
1701const getObjects = (base) => {
1702 return (objectIDs, requestOptions) => {
1703 const { attributesToRetrieve, ...options } = requestOptions || {};
1704 const requests = objectIDs.map(objectID => {
1705 return {
1706 indexName: base.indexName,
1707 objectID,
1708 ...(attributesToRetrieve ? { attributesToRetrieve } : {}),
1709 };
1710 });
1711 return base.transporter.read({
1712 method: MethodEnum.Post,
1713 path: '1/indexes/*/objects',
1714 data: {
1715 requests,
1716 },
1717 }, options);
1718 };
1719};
1720
1721const getRule = (base) => {
1722 return (objectID, requestOptions) => {
1723 return base.transporter.read({
1724 method: MethodEnum.Get,
1725 path: encode('1/indexes/%s/rules/%s', base.indexName, objectID),
1726 }, requestOptions);
1727 };
1728};
1729
1730const getSettings = (base) => {
1731 return (requestOptions) => {
1732 return base.transporter.read({
1733 method: MethodEnum.Get,
1734 path: encode('1/indexes/%s/settings', base.indexName),
1735 data: {
1736 getVersion: 2,
1737 },
1738 }, requestOptions);
1739 };
1740};
1741
1742const getSynonym = (base) => {
1743 return (objectID, requestOptions) => {
1744 return base.transporter.read({
1745 method: MethodEnum.Get,
1746 path: encode(`1/indexes/%s/synonyms/%s`, base.indexName, objectID),
1747 }, requestOptions);
1748 };
1749};
1750
1751const getTask = (base) => {
1752 return (taskID, requestOptions) => {
1753 return base.transporter.read({
1754 method: MethodEnum.Get,
1755 path: encode('1/indexes/%s/task/%s', base.indexName, taskID.toString()),
1756 }, requestOptions);
1757 };
1758};
1759
1760const partialUpdateObject = (base) => {
1761 return (object, requestOptions) => {
1762 return createWaitablePromise(partialUpdateObjects(base)([object], requestOptions).then(response => {
1763 return {
1764 objectID: response.objectIDs[0],
1765 taskID: response.taskIDs[0],
1766 };
1767 }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1768 };
1769};
1770
1771const partialUpdateObjects = (base) => {
1772 return (objects, requestOptions) => {
1773 const { createIfNotExists, ...options } = requestOptions || {};
1774 const action = createIfNotExists
1775 ? BatchActionEnum.PartialUpdateObject
1776 : BatchActionEnum.PartialUpdateObjectNoCreate;
1777 return chunkedBatch(base)(objects, action, options);
1778 };
1779};
1780
1781const replaceAllObjects = (base) => {
1782 return (objects, requestOptions) => {
1783 const { safe, autoGenerateObjectIDIfNotExist, batchSize, ...options } = requestOptions || {};
1784 const operation = (from, to, type, operationRequestOptions) => {
1785 return createWaitablePromise(base.transporter.write({
1786 method: MethodEnum.Post,
1787 path: encode('1/indexes/%s/operation', from),
1788 data: {
1789 operation: type,
1790 destination: to,
1791 },
1792 }, operationRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1793 };
1794 const randomSuffix = Math.random()
1795 .toString(36)
1796 .substring(7);
1797 const temporaryIndexName = `${base.indexName}_tmp_${randomSuffix}`;
1798 const saveObjectsInTemporary = saveObjects({
1799 appId: base.appId,
1800 transporter: base.transporter,
1801 indexName: temporaryIndexName,
1802 });
1803 // @ts-ignore
1804 // eslint-disable-next-line prefer-const, functional/no-let, functional/prefer-readonly-type
1805 let responses = [];
1806 const copyWaitablePromise = operation(base.indexName, temporaryIndexName, 'copy', {
1807 ...options,
1808 scope: ['settings', 'synonyms', 'rules'],
1809 });
1810 // eslint-disable-next-line functional/immutable-data
1811 responses.push(copyWaitablePromise);
1812 const result = (safe
1813 ? copyWaitablePromise.wait(options)
1814 : copyWaitablePromise)
1815 .then(() => {
1816 const saveObjectsWaitablePromise = saveObjectsInTemporary(objects, {
1817 ...options,
1818 autoGenerateObjectIDIfNotExist,
1819 batchSize,
1820 });
1821 // eslint-disable-next-line functional/immutable-data
1822 responses.push(saveObjectsWaitablePromise);
1823 return safe ? saveObjectsWaitablePromise.wait(options) : saveObjectsWaitablePromise;
1824 })
1825 .then(() => {
1826 const moveWaitablePromise = operation(temporaryIndexName, base.indexName, 'move', options);
1827 // eslint-disable-next-line functional/immutable-data
1828 responses.push(moveWaitablePromise);
1829 return safe ? moveWaitablePromise.wait(options) : moveWaitablePromise;
1830 })
1831 .then(() => Promise.all(responses))
1832 .then(([copyResponse, saveObjectsResponse, moveResponse]) => {
1833 return {
1834 objectIDs: saveObjectsResponse.objectIDs,
1835 taskIDs: [copyResponse.taskID, ...saveObjectsResponse.taskIDs, moveResponse.taskID],
1836 };
1837 });
1838 return createWaitablePromise(result, (_, waitRequestOptions) => {
1839 return Promise.all(responses.map(response => response.wait(waitRequestOptions)));
1840 });
1841 };
1842};
1843
1844const replaceAllRules = (base) => {
1845 return (rules, requestOptions) => {
1846 return saveRules(base)(rules, {
1847 ...requestOptions,
1848 clearExistingRules: true,
1849 });
1850 };
1851};
1852
1853const replaceAllSynonyms = (base) => {
1854 return (synonyms, requestOptions) => {
1855 return saveSynonyms(base)(synonyms, {
1856 ...requestOptions,
1857 clearExistingSynonyms: true,
1858 });
1859 };
1860};
1861
1862const saveObject = (base) => {
1863 return (object, requestOptions) => {
1864 return createWaitablePromise(saveObjects(base)([object], requestOptions).then(response => {
1865 return {
1866 objectID: response.objectIDs[0],
1867 taskID: response.taskIDs[0],
1868 };
1869 }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1870 };
1871};
1872
1873const saveObjects = (base) => {
1874 return (objects, requestOptions) => {
1875 const { autoGenerateObjectIDIfNotExist, ...options } = requestOptions || {};
1876 const action = autoGenerateObjectIDIfNotExist
1877 ? BatchActionEnum.AddObject
1878 : BatchActionEnum.UpdateObject;
1879 if (action === BatchActionEnum.UpdateObject) {
1880 // eslint-disable-next-line functional/no-loop-statement
1881 for (const object of objects) {
1882 if (object.objectID === undefined) {
1883 return createWaitablePromise(Promise.reject(createMissingObjectIDError()));
1884 }
1885 }
1886 }
1887 return chunkedBatch(base)(objects, action, options);
1888 };
1889};
1890
1891const saveRule = (base) => {
1892 return (rule, requestOptions) => {
1893 return saveRules(base)([rule], requestOptions);
1894 };
1895};
1896
1897const saveRules = (base) => {
1898 return (rules, requestOptions) => {
1899 const { forwardToReplicas, clearExistingRules, ...options } = requestOptions || {};
1900 const mappedRequestOptions = createMappedRequestOptions(options);
1901 if (forwardToReplicas) {
1902 mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
1903 }
1904 if (clearExistingRules) {
1905 mappedRequestOptions.queryParameters.clearExistingRules = 1; // eslint-disable-line functional/immutable-data
1906 }
1907 return createWaitablePromise(base.transporter.write({
1908 method: MethodEnum.Post,
1909 path: encode('1/indexes/%s/rules/batch', base.indexName),
1910 data: rules,
1911 }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1912 };
1913};
1914
1915const saveSynonym = (base) => {
1916 return (synonym, requestOptions) => {
1917 return saveSynonyms(base)([synonym], requestOptions);
1918 };
1919};
1920
1921const saveSynonyms = (base) => {
1922 return (synonyms, requestOptions) => {
1923 const { forwardToReplicas, clearExistingSynonyms, replaceExistingSynonyms, ...options } = requestOptions || {};
1924 const mappedRequestOptions = createMappedRequestOptions(options);
1925 if (forwardToReplicas) {
1926 mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
1927 }
1928 if (replaceExistingSynonyms || clearExistingSynonyms) {
1929 mappedRequestOptions.queryParameters.replaceExistingSynonyms = 1; // eslint-disable-line functional/immutable-data
1930 }
1931 return createWaitablePromise(base.transporter.write({
1932 method: MethodEnum.Post,
1933 path: encode('1/indexes/%s/synonyms/batch', base.indexName),
1934 data: synonyms,
1935 }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
1936 };
1937};
1938
1939const search = (base) => {
1940 return (query, requestOptions) => {
1941 return base.transporter.read({
1942 method: MethodEnum.Post,
1943 path: encode('1/indexes/%s/query', base.indexName),
1944 data: {
1945 query,
1946 },
1947 cacheable: true,
1948 }, requestOptions);
1949 };
1950};
1951
1952const searchForFacetValues = (base) => {
1953 return (facetName, facetQuery, requestOptions) => {
1954 return base.transporter.read({
1955 method: MethodEnum.Post,
1956 path: encode('1/indexes/%s/facets/%s/query', base.indexName, facetName),
1957 data: {
1958 facetQuery,
1959 },
1960 cacheable: true,
1961 }, requestOptions);
1962 };
1963};
1964
1965const searchRules = (base) => {
1966 return (query, requestOptions) => {
1967 return base.transporter.read({
1968 method: MethodEnum.Post,
1969 path: encode('1/indexes/%s/rules/search', base.indexName),
1970 data: {
1971 query,
1972 },
1973 }, requestOptions);
1974 };
1975};
1976
1977const searchSynonyms = (base) => {
1978 return (query, requestOptions) => {
1979 return base.transporter.read({
1980 method: MethodEnum.Post,
1981 path: encode('1/indexes/%s/synonyms/search', base.indexName),
1982 data: {
1983 query,
1984 },
1985 }, requestOptions);
1986 };
1987};
1988
1989const setSettings = (base) => {
1990 return (settings, requestOptions) => {
1991 const { forwardToReplicas, ...options } = requestOptions || {};
1992 const mappedRequestOptions = createMappedRequestOptions(options);
1993 if (forwardToReplicas) {
1994 mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
1995 }
1996 return createWaitablePromise(base.transporter.write({
1997 method: MethodEnum.Put,
1998 path: encode('1/indexes/%s/settings', base.indexName),
1999 data: settings,
2000 }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
2001 };
2002};
2003
2004const waitTask = (base) => {
2005 return (taskID, requestOptions) => {
2006 return createRetryablePromise(retry => {
2007 return getTask(base)(taskID, requestOptions).then(response => {
2008 return response.status !== 'published' ? retry() : undefined;
2009 });
2010 });
2011 };
2012};
2013
2014const BatchActionEnum = {
2015 AddObject: 'addObject',
2016 UpdateObject: 'updateObject',
2017 PartialUpdateObject: 'partialUpdateObject',
2018 PartialUpdateObjectNoCreate: 'partialUpdateObjectNoCreate',
2019 DeleteObject: 'deleteObject',
2020 DeleteIndex: 'delete',
2021 ClearIndex: 'clear',
2022};
2023
2024const ScopeEnum = {
2025 Settings: 'settings',
2026 Synonyms: 'synonyms',
2027 Rules: 'rules',
2028};
2029
2030const LogLevelEnum = {
2031 Debug: 1,
2032 Info: 2,
2033 Error: 3,
2034};
2035
2036/* eslint no-console: 0 */
2037function createConsoleLogger(logLevel) {
2038 return {
2039 debug(message, args) {
2040 if (LogLevelEnum.Debug >= logLevel) {
2041 console.debug(message, args);
2042 }
2043 return Promise.resolve();
2044 },
2045 info(message, args) {
2046 if (LogLevelEnum.Info >= logLevel) {
2047 console.info(message, args);
2048 }
2049 return Promise.resolve();
2050 },
2051 error(message, args) {
2052 console.error(message, args);
2053 return Promise.resolve();
2054 },
2055 };
2056}
2057
2058const getRecommendations = base => {
2059 return (queries, requestOptions) => {
2060 const requests = queries.map(query => ({
2061 ...query,
2062 // The `threshold` param is required by the endpoint to make it easier
2063 // to provide a default value later, so we default it in the client
2064 // so that users don't have to provide a value.
2065 threshold: query.threshold || 0,
2066 }));
2067 return base.transporter.read({
2068 method: MethodEnum.Post,
2069 path: '1/indexes/*/recommendations',
2070 data: {
2071 requests,
2072 },
2073 cacheable: true,
2074 }, requestOptions);
2075 };
2076};
2077
2078const getFrequentlyBoughtTogether = base => {
2079 return (queries, requestOptions) => {
2080 return getRecommendations(base)(queries.map(query => ({
2081 ...query,
2082 fallbackParameters: {},
2083 model: 'bought-together',
2084 })), requestOptions);
2085 };
2086};
2087
2088const getRelatedProducts = base => {
2089 return (queries, requestOptions) => {
2090 return getRecommendations(base)(queries.map(query => ({
2091 ...query,
2092 model: 'related-products',
2093 })), requestOptions);
2094 };
2095};
2096
2097const getTrendingFacets = base => {
2098 return (queries, requestOptions) => {
2099 const requests = queries.map(query => ({
2100 ...query,
2101 model: 'trending-facets',
2102 // The `threshold` param is required by the endpoint to make it easier
2103 // to provide a default value later, so we default it in the client
2104 // so that users don't have to provide a value.
2105 threshold: query.threshold || 0,
2106 }));
2107 return base.transporter.read({
2108 method: MethodEnum.Post,
2109 path: '1/indexes/*/recommendations',
2110 data: {
2111 requests,
2112 },
2113 cacheable: true,
2114 }, requestOptions);
2115 };
2116};
2117
2118const getTrendingItems = base => {
2119 return (queries, requestOptions) => {
2120 const requests = queries.map(query => ({
2121 ...query,
2122 model: 'trending-items',
2123 // The `threshold` param is required by the endpoint to make it easier
2124 // to provide a default value later, so we default it in the client
2125 // so that users don't have to provide a value.
2126 threshold: query.threshold || 0,
2127 }));
2128 return base.transporter.read({
2129 method: MethodEnum.Post,
2130 path: '1/indexes/*/recommendations',
2131 data: {
2132 requests,
2133 },
2134 cacheable: true,
2135 }, requestOptions);
2136 };
2137};
2138
2139const getLookingSimilar = base => {
2140 return (queries, requestOptions) => {
2141 return getRecommendations(base)(queries.map(query => ({
2142 ...query,
2143 model: 'looking-similar',
2144 })), requestOptions);
2145 };
2146};
2147
2148const getRecommendedForYou = base => {
2149 return (queries, requestOptions) => {
2150 const requests = queries.map(query => ({
2151 ...query,
2152 model: 'recommended-for-you',
2153 threshold: query.threshold || 0,
2154 }));
2155 return base.transporter.read({
2156 method: MethodEnum.Post,
2157 path: '1/indexes/*/recommendations',
2158 data: {
2159 requests,
2160 },
2161 cacheable: true,
2162 }, requestOptions);
2163 };
2164};
2165
2166function createBrowserXhrRequester() {
2167 return {
2168 send(request) {
2169 return new Promise((resolve) => {
2170 const baseRequester = new XMLHttpRequest();
2171 baseRequester.open(request.method, request.url, true);
2172 Object.keys(request.headers).forEach(key => baseRequester.setRequestHeader(key, request.headers[key]));
2173 const createTimeout = (timeout, content) => {
2174 return setTimeout(() => {
2175 baseRequester.abort();
2176 resolve({
2177 status: 0,
2178 content,
2179 isTimedOut: true,
2180 });
2181 }, timeout * 1000);
2182 };
2183 const connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout');
2184 // eslint-disable-next-line functional/no-let
2185 let responseTimeout;
2186 // eslint-disable-next-line functional/immutable-data
2187 baseRequester.onreadystatechange = () => {
2188 if (baseRequester.readyState > baseRequester.OPENED && responseTimeout === undefined) {
2189 clearTimeout(connectTimeout);
2190 responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout');
2191 }
2192 };
2193 // eslint-disable-next-line functional/immutable-data
2194 baseRequester.onerror = () => {
2195 // istanbul ignore next
2196 if (baseRequester.status === 0) {
2197 clearTimeout(connectTimeout);
2198 clearTimeout(responseTimeout);
2199 resolve({
2200 content: baseRequester.responseText || 'Network request failed',
2201 status: baseRequester.status,
2202 isTimedOut: false,
2203 });
2204 }
2205 };
2206 // eslint-disable-next-line functional/immutable-data
2207 baseRequester.onload = () => {
2208 clearTimeout(connectTimeout);
2209 clearTimeout(responseTimeout);
2210 resolve({
2211 content: baseRequester.responseText,
2212 status: baseRequester.status,
2213 isTimedOut: false,
2214 });
2215 };
2216 baseRequester.send(request.data);
2217 });
2218 },
2219 };
2220}
2221
2222function algoliasearch(appId, apiKey, options) {
2223 const commonOptions = {
2224 appId,
2225 apiKey,
2226 timeouts: {
2227 connect: 1,
2228 read: 2,
2229 write: 30,
2230 },
2231 requester: createBrowserXhrRequester(),
2232 logger: createConsoleLogger(LogLevelEnum.Error),
2233 responsesCache: createInMemoryCache(),
2234 requestsCache: createInMemoryCache({ serializable: false }),
2235 hostsCache: createFallbackableCache({
2236 caches: [
2237 createBrowserLocalStorageCache({ key: `${version}-${appId}` }),
2238 createInMemoryCache(),
2239 ],
2240 }),
2241 userAgent: createUserAgent(version).add({ segment: 'Browser' }),
2242 };
2243 const searchClientOptions = { ...commonOptions, ...options };
2244 const initPersonalization = () => (clientOptions) => {
2245 return createPersonalizationClient({
2246 ...commonOptions,
2247 ...clientOptions,
2248 methods: {
2249 getPersonalizationStrategy,
2250 setPersonalizationStrategy,
2251 },
2252 });
2253 };
2254 return createSearchClient({
2255 ...searchClientOptions,
2256 methods: {
2257 search: multipleQueries,
2258 searchForFacetValues: multipleSearchForFacetValues,
2259 multipleBatch,
2260 multipleGetObjects,
2261 multipleQueries,
2262 copyIndex,
2263 copySettings,
2264 copySynonyms,
2265 copyRules,
2266 moveIndex,
2267 listIndices,
2268 getLogs,
2269 listClusters,
2270 multipleSearchForFacetValues,
2271 getApiKey,
2272 addApiKey,
2273 listApiKeys,
2274 updateApiKey,
2275 deleteApiKey,
2276 restoreApiKey,
2277 assignUserID,
2278 assignUserIDs,
2279 getUserID,
2280 searchUserIDs,
2281 listUserIDs,
2282 getTopUserIDs,
2283 removeUserID,
2284 hasPendingMappings,
2285 clearDictionaryEntries,
2286 deleteDictionaryEntries,
2287 getDictionarySettings,
2288 getAppTask,
2289 replaceDictionaryEntries,
2290 saveDictionaryEntries,
2291 searchDictionaryEntries,
2292 setDictionarySettings,
2293 waitAppTask,
2294 customRequest,
2295 initIndex: base => (indexName) => {
2296 return initIndex(base)(indexName, {
2297 methods: {
2298 batch,
2299 delete: deleteIndex,
2300 findAnswers,
2301 getObject,
2302 getObjects,
2303 saveObject,
2304 saveObjects,
2305 search,
2306 searchForFacetValues,
2307 waitTask,
2308 setSettings,
2309 getSettings,
2310 partialUpdateObject,
2311 partialUpdateObjects,
2312 deleteObject,
2313 deleteObjects,
2314 deleteBy,
2315 clearObjects,
2316 browseObjects,
2317 getObjectPosition,
2318 findObject,
2319 exists,
2320 saveSynonym,
2321 saveSynonyms,
2322 getSynonym,
2323 searchSynonyms,
2324 browseSynonyms,
2325 deleteSynonym,
2326 clearSynonyms,
2327 replaceAllObjects,
2328 replaceAllSynonyms,
2329 searchRules,
2330 getRule,
2331 deleteRule,
2332 saveRule,
2333 saveRules,
2334 replaceAllRules,
2335 browseRules,
2336 clearRules,
2337 },
2338 });
2339 },
2340 initAnalytics: () => (clientOptions) => {
2341 return createAnalyticsClient({
2342 ...commonOptions,
2343 ...clientOptions,
2344 methods: {
2345 addABTest,
2346 getABTest,
2347 getABTests,
2348 stopABTest,
2349 deleteABTest,
2350 },
2351 });
2352 },
2353 initPersonalization,
2354 initRecommendation: () => (clientOptions) => {
2355 searchClientOptions.logger.info('The `initRecommendation` method is deprecated. Use `initPersonalization` instead.');
2356 return initPersonalization()(clientOptions);
2357 },
2358 getRecommendations,
2359 getFrequentlyBoughtTogether,
2360 getLookingSimilar,
2361 getRecommendedForYou,
2362 getRelatedProducts,
2363 getTrendingFacets,
2364 getTrendingItems,
2365 },
2366 });
2367}
2368// eslint-disable-next-line functional/immutable-data
2369algoliasearch.version = version;
2370
2371export default algoliasearch;