text: 'use strict';
  
  Object.defineProperty(exports, '__esModule', { value: true });
  
  var clientCommon = require('@algolia/client-common');
  var transporter = require('@algolia/transporter');
  var requesterCommon = require('@algolia/requester-common');
  var crypto = require('crypto');
  
  function createBrowsablePromise(options) {
      const browse = (data) => {
          return options.request(data).then(response => {
              /**
               * First we send to the developer the
               * batch retrieved from the API.
               */
              if (options.batch !== undefined) {
                  options.batch(response.hits);
              }
              /**
               * Then, we ask to the browse concrete implementation
               * if we should stop browsing. As example, the `browseObjects`
               * method will stop if the cursor is not present on the response.
               */
              if (options.shouldStop(response)) {
                  return undefined;
              }
              /**
               * Finally, if the response contains a cursor, we browse to the next
               * batch using that same cursor. Otherwise, we just use the traditional
               * browsing using the page element.
               */
              if (response.cursor) {
                  return browse({
                      cursor: response.cursor,
                  });
              }
              return browse({
                  page: (data.page || 0) + 1,
              });
          });
      };
      return browse({});
  }
  
  const createSearchClient = options => {
      const appId = options.appId;
      const auth = clientCommon.createAuth(options.authMode !== undefined ? options.authMode : clientCommon.AuthMode.WithinHeaders, appId, options.apiKey);
      const transporter$1 = transporter.createTransporter({
          hosts: [
              { url: `${appId}-dsn.algolia.net`, accept: transporter.CallEnum.Read },
              { url: `${appId}.algolia.net`, accept: transporter.CallEnum.Write },
          ].concat(clientCommon.shuffle([
              { url: `${appId}-1.algolianet.com` },
              { url: `${appId}-2.algolianet.com` },
              { url: `${appId}-3.algolianet.com` },
          ])),
          ...options,
          headers: {
              ...auth.headers(),
              ...{ 'content-type': 'application/x-www-form-urlencoded' },
              ...options.headers,
          },
          queryParameters: {
              ...auth.queryParameters(),
              ...options.queryParameters,
          },
      });
      const base = {
          transporter: transporter$1,
          appId,
          addAlgoliaAgent(segment, version) {
              transporter$1.userAgent.add({ segment, version });
          },
          clearCache() {
              return Promise.all([
                  transporter$1.requestsCache.clear(),
                  transporter$1.responsesCache.clear(),
              ]).then(() => undefined);
          },
      };
      return clientCommon.addMethods(base, options.methods);
  };
  
  function createMissingObjectIDError() {
      return {
          name: 'MissingObjectIDError',
          message: 'All objects must have an unique objectID ' +
              '(like a primary key) to be valid. ' +
              'Algolia is also able to generate objectIDs ' +
              "automatically but *it's not recommended*. " +
              "To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option.",
      };
  }
  
  function createObjectNotFoundError() {
      return {
          name: 'ObjectNotFoundError',
          message: 'Object not found.',
      };
  }
  
  function createValidUntilNotFoundError() {
      return {
          name: 'ValidUntilNotFoundError',
          message: 'ValidUntil not found in given secured api key.',
      };
  }
  
  const addApiKey = (base) => {
      return (acl, requestOptions) => {
          const { queryParameters, ...options } = requestOptions || {};
          const data = {
              acl,
              ...(queryParameters !== undefined ? { queryParameters } : {}),
          };
          const wait = (response, waitRequestOptions) => {
              return clientCommon.createRetryablePromise(retry => {
                  return getApiKey(base)(response.key, waitRequestOptions).catch((apiError) => {
                      if (apiError.status !== 404) {
                          throw apiError;
                      }
                      return retry();
                  });
              });
          };
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: '1/keys',
              data,
          }, options), wait);
      };
  };
  
  const assignUserID = (base) => {
      return (userID, clusterName, requestOptions) => {
          const mappedRequestOptions = transporter.createMappedRequestOptions(requestOptions);
          // eslint-disable-next-line functional/immutable-data
          mappedRequestOptions.queryParameters['X-Algolia-User-ID'] = userID;
          return base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: '1/clusters/mapping',
              data: { cluster: clusterName },
          }, mappedRequestOptions);
      };
  };
  
  const assignUserIDs = (base) => {
      return (userIDs, clusterName, requestOptions) => {
          return base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: '1/clusters/mapping/batch',
              data: {
                  users: userIDs,
                  cluster: clusterName,
              },
          }, requestOptions);
      };
  };
  
  const clearDictionaryEntries = (base) => {
      return (dictionary, requestOptions) => {
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('/1/dictionaries/%s/batch', dictionary),
              data: {
                  clearExistingDictionaryEntries: true,
                  requests: { action: 'addEntry', body: [] },
              },
          }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const copyIndex = (base) => {
      return (from, to, requestOptions) => {
          const wait = (response, waitRequestOptions) => {
              return initIndex(base)(from, {
                  methods: { waitTask },
              }).waitTask(response.taskID, waitRequestOptions);
          };
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/operation', from),
              data: {
                  operation: 'copy',
                  destination: to,
              },
          }, requestOptions), wait);
      };
  };
  
  const copyRules = (base) => {
      return (from, to, requestOptions) => {
          return copyIndex(base)(from, to, {
              ...requestOptions,
              scope: [ScopeEnum.Rules],
          });
      };
  };
  
  const copySettings = (base) => {
      return (from, to, requestOptions) => {
          return copyIndex(base)(from, to, {
              ...requestOptions,
              scope: [ScopeEnum.Settings],
          });
      };
  };
  
  const copySynonyms = (base) => {
      return (from, to, requestOptions) => {
          return copyIndex(base)(from, to, {
              ...requestOptions,
              scope: [ScopeEnum.Synonyms],
          });
      };
  };
  
  const deleteApiKey = (base) => {
      return (apiKey, requestOptions) => {
          const wait = (_, waitRequestOptions) => {
              return clientCommon.createRetryablePromise(retry => {
                  return getApiKey(base)(apiKey, waitRequestOptions)
                      .then(retry)
                      .catch((apiError) => {
                      if (apiError.status !== 404) {
                          throw apiError;
                      }
                  });
              });
          };
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Delete,
              path: clientCommon.encode('1/keys/%s', apiKey),
          }, requestOptions), wait);
      };
  };
  
  const deleteDictionaryEntries = (base) => {
      return (dictionary, objectIDs, requestOptions) => {
          const requests = objectIDs.map(objectID => ({
              action: 'deleteEntry',
              body: { objectID },
          }));
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('/1/dictionaries/%s/batch', dictionary),
              data: { clearExistingDictionaryEntries: false, requests },
          }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const generateSecuredApiKey = () => {
      return (parentApiKey, restrictions) => {
          const queryParameters = transporter.serializeQueryParameters(restrictions);
          const securedKey = crypto.createHmac('sha256', parentApiKey)
              .update(queryParameters)
              .digest('hex');
          return Buffer.from(securedKey + queryParameters).toString('base64');
      };
  };
  
  const getApiKey = (base) => {
      return (apiKey, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: clientCommon.encode('1/keys/%s', apiKey),
          }, requestOptions);
      };
  };
  
  const getDictionarySettings = (base) => {
      return (requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: '/1/dictionaries/*/settings',
          }, requestOptions);
      };
  };
  
  const getLogs = (base) => {
      return (requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: '1/logs',
          }, requestOptions);
      };
  };
  
  const getSecuredApiKeyRemainingValidity = () => {
      return (securedApiKey) => {
          const decodedString = Buffer.from(securedApiKey, 'base64').toString('ascii');
          const regex = /validUntil=(\d+)/;
          const match = decodedString.match(regex);
          if (match === null) {
              throw createValidUntilNotFoundError();
          }
          return parseInt(match[1], 10) - Math.round(new Date().getTime() / 1000);
      };
  };
  
  const getTopUserIDs = (base) => {
      return (requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: '1/clusters/mapping/top',
          }, requestOptions);
      };
  };
  
  const getAppTask = (base) => {
      return (taskID, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: clientCommon.encode('1/task/%s', taskID.toString()),
          }, requestOptions);
      };
  };
  
  const getUserID = (base) => {
      return (userID, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: clientCommon.encode('1/clusters/mapping/%s', userID),
          }, requestOptions);
      };
  };
  
  const hasPendingMappings = (base) => {
      return (requestOptions) => {
          const { retrieveMappings, ...options } = requestOptions || {};
          if (retrieveMappings === true) {
              // eslint-disable-next-line functional/immutable-data
              options.getClusters = true;
          }
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: '1/clusters/mapping/pending',
          }, options);
      };
  };
  
  const initIndex = (base) => {
      return (indexName, options = {}) => {
          const searchIndex = {
              transporter: base.transporter,
              appId: base.appId,
              indexName,
          };
          return clientCommon.addMethods(searchIndex, options.methods);
      };
  };
  
  const listApiKeys = (base) => {
      return (requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: '1/keys',
          }, requestOptions);
      };
  };
  
  const listClusters = (base) => {
      return (requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: '1/clusters',
          }, requestOptions);
      };
  };
  
  const listIndices = (base) => {
      return (requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: '1/indexes',
          }, requestOptions);
      };
  };
  
  const listUserIDs = (base) => {
      return (requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: '1/clusters/mapping',
          }, requestOptions);
      };
  };
  
  const moveIndex = (base) => {
      return (from, to, requestOptions) => {
          const wait = (response, waitRequestOptions) => {
              return initIndex(base)(from, {
                  methods: { waitTask },
              }).waitTask(response.taskID, waitRequestOptions);
          };
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/operation', from),
              data: {
                  operation: 'move',
                  destination: to,
              },
          }, requestOptions), wait);
      };
  };
  
  const multipleBatch = (base) => {
      return (requests, requestOptions) => {
          const wait = (response, waitRequestOptions) => {
              return Promise.all(Object.keys(response.taskID).map(indexName => {
                  return initIndex(base)(indexName, {
                      methods: { waitTask },
                  }).waitTask(response.taskID[indexName], waitRequestOptions);
              }));
          };
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: '1/indexes/*/batch',
              data: {
                  requests,
              },
          }, requestOptions), wait);
      };
  };
  
  const multipleGetObjects = (base) => {
      return (requests, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: '1/indexes/*/objects',
              data: {
                  requests,
              },
          }, requestOptions);
      };
  };
  
  const multipleQueries = (base) => {
      return (queries, requestOptions) => {
          const requests = queries.map(query => {
              return {
                  ...query,
                  params: transporter.serializeQueryParameters(query.params || {}),
              };
          });
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: '1/indexes/*/queries',
              data: {
                  requests,
              },
              cacheable: true,
          }, requestOptions);
      };
  };
  
  const multipleSearchForFacetValues = (base) => {
      return (queries, requestOptions) => {
          return Promise.all(queries.map(query => {
              const { facetName, facetQuery, ...params } = query.params;
              return initIndex(base)(query.indexName, {
                  methods: { searchForFacetValues },
              }).searchForFacetValues(facetName, facetQuery, {
                  ...requestOptions,
                  ...params,
              });
          }));
      };
  };
  
  const removeUserID = (base) => {
      return (userID, requestOptions) => {
          const mappedRequestOptions = transporter.createMappedRequestOptions(requestOptions);
          // eslint-disable-next-line functional/immutable-data
          mappedRequestOptions.queryParameters['X-Algolia-User-ID'] = userID;
          return base.transporter.write({
              method: requesterCommon.MethodEnum.Delete,
              path: '1/clusters/mapping',
          }, mappedRequestOptions);
      };
  };
  
  const replaceDictionaryEntries = (base) => {
      return (dictionary, entries, requestOptions) => {
          const requests = entries.map(entry => ({
              action: 'addEntry',
              body: entry,
          }));
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('/1/dictionaries/%s/batch', dictionary),
              data: { clearExistingDictionaryEntries: true, requests },
          }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const restoreApiKey = (base) => {
      return (apiKey, requestOptions) => {
          const wait = (_, waitRequestOptions) => {
              return clientCommon.createRetryablePromise(retry => {
                  return getApiKey(base)(apiKey, waitRequestOptions).catch((apiError) => {
                      if (apiError.status !== 404) {
                          throw apiError;
                      }
                      return retry();
                  });
              });
          };
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/keys/%s/restore', apiKey),
          }, requestOptions), wait);
      };
  };
  
  const saveDictionaryEntries = (base) => {
      return (dictionary, entries, requestOptions) => {
          const requests = entries.map(entry => ({
              action: 'addEntry',
              body: entry,
          }));
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('/1/dictionaries/%s/batch', dictionary),
              data: { clearExistingDictionaryEntries: false, requests },
          }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const searchDictionaryEntries = (base) => {
      return (dictionary, query, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('/1/dictionaries/%s/search', dictionary),
              data: {
                  query,
              },
              cacheable: true,
          }, requestOptions);
      };
  };
  
  const searchUserIDs = (base) => {
      return (query, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: '1/clusters/mapping/search',
              data: {
                  query,
              },
          }, requestOptions);
      };
  };
  
  const setDictionarySettings = (base) => {
      return (settings, requestOptions) => {
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Put,
              path: '/1/dictionaries/*/settings',
              data: settings,
          }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const updateApiKey = (base) => {
      return (apiKey, requestOptions) => {
          const updatedFields = Object.assign({}, requestOptions);
          const { queryParameters, ...options } = requestOptions || {};
          const data = queryParameters ? { queryParameters } : {};
          const apiKeyFields = [
              'acl',
              'indexes',
              'referers',
              'restrictSources',
              'queryParameters',
              'description',
              'maxQueriesPerIPPerHour',
              'maxHitsPerQuery',
          ];
          const hasChanged = (getApiKeyResponse) => {
              return Object.keys(updatedFields)
                  .filter((updatedField) => apiKeyFields.indexOf(updatedField) !== -1)
                  .every(updatedField => {
                  return getApiKeyResponse[updatedField] === updatedFields[updatedField];
              });
          };
          const wait = (_, waitRequestOptions) => clientCommon.createRetryablePromise(retry => {
              return getApiKey(base)(apiKey, waitRequestOptions).then(getApiKeyResponse => {
                  return hasChanged(getApiKeyResponse) ? Promise.resolve() : retry();
              });
          });
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Put,
              path: clientCommon.encode('1/keys/%s', apiKey),
              data,
          }, options), wait);
      };
  };
  
  const waitAppTask = (base) => {
      return (taskID, requestOptions) => {
          return clientCommon.createRetryablePromise(retry => {
              return getAppTask(base)(taskID, requestOptions).then(response => {
                  return response.status !== 'published' ? retry() : undefined;
              });
          });
      };
  };
  
  const batch = (base) => {
      return (requests, requestOptions) => {
          const wait = (response, waitRequestOptions) => {
              return waitTask(base)(response.taskID, waitRequestOptions);
          };
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/batch', base.indexName),
              data: {
                  requests,
              },
          }, requestOptions), wait);
      };
  };
  
  const browseObjects = (base) => {
      return (requestOptions) => {
          return createBrowsablePromise({
              shouldStop: response => response.cursor === undefined,
              ...requestOptions,
              request: (data) => base.transporter.read({
                  method: requesterCommon.MethodEnum.Post,
                  path: clientCommon.encode('1/indexes/%s/browse', base.indexName),
                  data,
              }, requestOptions),
          });
      };
  };
  
  const browseRules = (base) => {
      return (requestOptions) => {
          const options = {
              hitsPerPage: 1000,
              ...requestOptions,
          };
          return createBrowsablePromise({
              shouldStop: response => response.hits.length < options.hitsPerPage,
              ...options,
              request(data) {
                  return searchRules(base)('', { ...options, ...data }).then((response) => {
                      return {
                          ...response,
                          hits: response.hits.map(rule => {
                              // eslint-disable-next-line functional/immutable-data,no-param-reassign
                              delete rule._highlightResult;
                              return rule;
                          }),
                      };
                  });
              },
          });
      };
  };
  
  const browseSynonyms = (base) => {
      return (requestOptions) => {
          const options = {
              hitsPerPage: 1000,
              ...requestOptions,
          };
          return createBrowsablePromise({
              shouldStop: response => response.hits.length < options.hitsPerPage,
              ...options,
              request(data) {
                  return searchSynonyms(base)('', { ...options, ...data }).then((response) => {
                      return {
                          ...response,
                          hits: response.hits.map(synonym => {
                              // eslint-disable-next-line functional/immutable-data,no-param-reassign
                              delete synonym._highlightResult;
                              return synonym;
                          }),
                      };
                  });
              },
          });
      };
  };
  
  const chunkedBatch = (base) => {
      return (bodies, action, requestOptions) => {
          const { batchSize, ...options } = requestOptions || {};
          const response = {
              taskIDs: [],
              objectIDs: [],
          };
          const forEachBatch = (lastIndex = 0) => {
              // eslint-disable-next-line functional/prefer-readonly-type
              const bodiesChunk = [];
              // eslint-disable-next-line functional/no-let
              let index;
              /* eslint-disable-next-line functional/no-loop-statement */
              for (index = lastIndex; index < bodies.length; index++) {
                  // eslint-disable-next-line functional/immutable-data
                  bodiesChunk.push(bodies[index]);
                  if (bodiesChunk.length === (batchSize || 1000)) {
                      break;
                  }
              }
              if (bodiesChunk.length === 0) {
                  return Promise.resolve(response);
              }
              return batch(base)(bodiesChunk.map(body => {
                  return {
                      action,
                      body,
                  };
              }), options).then(res => {
                  response.objectIDs = response.objectIDs.concat(res.objectIDs); // eslint-disable-line functional/immutable-data
                  response.taskIDs.push(res.taskID); // eslint-disable-line functional/immutable-data
                  index++;
                  return forEachBatch(index);
              });
          };
          return clientCommon.createWaitablePromise(forEachBatch(), (chunkedBatchResponse, waitRequestOptions) => {
              return Promise.all(chunkedBatchResponse.taskIDs.map(taskID => {
                  return waitTask(base)(taskID, waitRequestOptions);
              }));
          });
      };
  };
  
  const clearObjects = (base) => {
      return (requestOptions) => {
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/clear', base.indexName),
          }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const clearRules = (base) => {
      return (requestOptions) => {
          const { forwardToReplicas, ...options } = requestOptions || {};
          const mappedRequestOptions = transporter.createMappedRequestOptions(options);
          if (forwardToReplicas) {
              mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
          }
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/rules/clear', base.indexName),
          }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const clearSynonyms = (base) => {
      return (requestOptions) => {
          const { forwardToReplicas, ...options } = requestOptions || {};
          const mappedRequestOptions = transporter.createMappedRequestOptions(options);
          if (forwardToReplicas) {
              mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
          }
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/synonyms/clear', base.indexName),
          }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const deleteBy = (base) => {
      return (filters, requestOptions) => {
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/deleteByQuery', base.indexName),
              data: filters,
          }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const deleteIndex = (base) => {
      return (requestOptions) => {
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Delete,
              path: clientCommon.encode('1/indexes/%s', base.indexName),
          }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const deleteObject = (base) => {
      return (objectID, requestOptions) => {
          return clientCommon.createWaitablePromise(deleteObjects(base)([objectID], requestOptions).then(response => {
              return { taskID: response.taskIDs[0] };
          }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const deleteObjects = (base) => {
      return (objectIDs, requestOptions) => {
          const objects = objectIDs.map(objectID => {
              return { objectID };
          });
          return chunkedBatch(base)(objects, BatchActionEnum.DeleteObject, requestOptions);
      };
  };
  
  const deleteRule = (base) => {
      return (objectID, requestOptions) => {
          const { forwardToReplicas, ...options } = requestOptions || {};
          const mappedRequestOptions = transporter.createMappedRequestOptions(options);
          if (forwardToReplicas) {
              mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
          }
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Delete,
              path: clientCommon.encode('1/indexes/%s/rules/%s', base.indexName, objectID),
          }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const deleteSynonym = (base) => {
      return (objectID, requestOptions) => {
          const { forwardToReplicas, ...options } = requestOptions || {};
          const mappedRequestOptions = transporter.createMappedRequestOptions(options);
          if (forwardToReplicas) {
              mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
          }
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Delete,
              path: clientCommon.encode('1/indexes/%s/synonyms/%s', base.indexName, objectID),
          }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const exists = (base) => {
      return (requestOptions) => {
          return getSettings(base)(requestOptions)
              .then(() => true)
              .catch(error => {
              if (error.status !== 404) {
                  throw error;
              }
              return false;
          });
      };
  };
  
  const findAnswers = (base) => {
      return (query, queryLanguages, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/answers/%s/prediction', base.indexName),
              data: {
                  query,
                  queryLanguages,
              },
              cacheable: true,
          }, requestOptions);
      };
  };
  
  const findObject = (base) => {
      return (callback, requestOptions) => {
          const { query, paginate, ...options } = requestOptions || {};
          // eslint-disable-next-line functional/no-let
          let page = 0;
          const forEachPage = () => {
              return search(base)(query || '', { ...options, page }).then(result => {
                  // eslint-disable-next-line functional/no-loop-statement
                  for (const [position, hit] of Object.entries(result.hits)) {
                      // eslint-disable-next-line promise/no-callback-in-promise
                      if (callback(hit)) {
                          return {
                              object: hit,
                              position: parseInt(position, 10),
                              page,
                          };
                      }
                  }
                  page++;
                  // paginate if option was set and has next page
                  if (paginate === false || page >= result.nbPages) {
                      throw createObjectNotFoundError();
                  }
                  return forEachPage();
              });
          };
          return forEachPage();
      };
  };
  
  const getObject = (base) => {
      return (objectID, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: clientCommon.encode('1/indexes/%s/%s', base.indexName, objectID),
          }, requestOptions);
      };
  };
  
  const getObjectPosition = () => {
      return (searchResponse, objectID) => {
          // eslint-disable-next-line functional/no-loop-statement
          for (const [position, hit] of Object.entries(searchResponse.hits)) {
              if (hit.objectID === objectID) {
                  return parseInt(position, 10);
              }
          }
          return -1;
      };
  };
  
  const getObjects = (base) => {
      return (objectIDs, requestOptions) => {
          const { attributesToRetrieve, ...options } = requestOptions || {};
          const requests = objectIDs.map(objectID => {
              return {
                  indexName: base.indexName,
                  objectID,
                  ...(attributesToRetrieve ? { attributesToRetrieve } : {}),
              };
          });
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: '1/indexes/*/objects',
              data: {
                  requests,
              },
          }, options);
      };
  };
  
  const getRule = (base) => {
      return (objectID, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: clientCommon.encode('1/indexes/%s/rules/%s', base.indexName, objectID),
          }, requestOptions);
      };
  };
  
  const getSettings = (base) => {
      return (requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: clientCommon.encode('1/indexes/%s/settings', base.indexName),
              data: {
                  getVersion: 2,
              },
          }, requestOptions);
      };
  };
  
  const getSynonym = (base) => {
      return (objectID, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: clientCommon.encode(`1/indexes/%s/synonyms/%s`, base.indexName, objectID),
          }, requestOptions);
      };
  };
  
  const getTask = (base) => {
      return (taskID, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Get,
              path: clientCommon.encode('1/indexes/%s/task/%s', base.indexName, taskID.toString()),
          }, requestOptions);
      };
  };
  
  const partialUpdateObject = (base) => {
      return (object, requestOptions) => {
          return clientCommon.createWaitablePromise(partialUpdateObjects(base)([object], requestOptions).then(response => {
              return {
                  objectID: response.objectIDs[0],
                  taskID: response.taskIDs[0],
              };
          }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const partialUpdateObjects = (base) => {
      return (objects, requestOptions) => {
          const { createIfNotExists, ...options } = requestOptions || {};
          const action = createIfNotExists
              ? BatchActionEnum.PartialUpdateObject
              : BatchActionEnum.PartialUpdateObjectNoCreate;
          return chunkedBatch(base)(objects, action, options);
      };
  };
  
  const replaceAllObjects = (base) => {
      return (objects, requestOptions) => {
          const { safe, autoGenerateObjectIDIfNotExist, batchSize, ...options } = requestOptions || {};
          const operation = (from, to, type, operationRequestOptions) => {
              return clientCommon.createWaitablePromise(base.transporter.write({
                  method: requesterCommon.MethodEnum.Post,
                  path: clientCommon.encode('1/indexes/%s/operation', from),
                  data: {
                      operation: type,
                      destination: to,
                  },
              }, operationRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
          };
          const randomSuffix = Math.random()
              .toString(36)
              .substring(7);
          const temporaryIndexName = `${base.indexName}_tmp_${randomSuffix}`;
          const saveObjectsInTemporary = saveObjects({
              appId: base.appId,
              transporter: base.transporter,
              indexName: temporaryIndexName,
          });
          // @ts-ignore
          // eslint-disable-next-line prefer-const, functional/no-let, functional/prefer-readonly-type
          let responses = [];
          const copyWaitablePromise = operation(base.indexName, temporaryIndexName, 'copy', {
              ...options,
              scope: ['settings', 'synonyms', 'rules'],
          });
          // eslint-disable-next-line functional/immutable-data
          responses.push(copyWaitablePromise);
          const result = (safe
              ? copyWaitablePromise.wait(options)
              : copyWaitablePromise)
              .then(() => {
              const saveObjectsWaitablePromise = saveObjectsInTemporary(objects, {
                  ...options,
                  autoGenerateObjectIDIfNotExist,
                  batchSize,
              });
              // eslint-disable-next-line functional/immutable-data
              responses.push(saveObjectsWaitablePromise);
              return safe ? saveObjectsWaitablePromise.wait(options) : saveObjectsWaitablePromise;
          })
              .then(() => {
              const moveWaitablePromise = operation(temporaryIndexName, base.indexName, 'move', options);
              // eslint-disable-next-line functional/immutable-data
              responses.push(moveWaitablePromise);
              return safe ? moveWaitablePromise.wait(options) : moveWaitablePromise;
          })
              .then(() => Promise.all(responses))
              .then(([copyResponse, saveObjectsResponse, moveResponse]) => {
              return {
                  objectIDs: saveObjectsResponse.objectIDs,
                  taskIDs: [copyResponse.taskID, ...saveObjectsResponse.taskIDs, moveResponse.taskID],
              };
          });
          return clientCommon.createWaitablePromise(result, (_, waitRequestOptions) => {
              return Promise.all(responses.map(response => response.wait(waitRequestOptions)));
          });
      };
  };
  
  const replaceAllRules = (base) => {
      return (rules, requestOptions) => {
          return saveRules(base)(rules, {
              ...requestOptions,
              clearExistingRules: true,
          });
      };
  };
  
  const replaceAllSynonyms = (base) => {
      return (synonyms, requestOptions) => {
          return saveSynonyms(base)(synonyms, {
              ...requestOptions,
              clearExistingSynonyms: true,
          });
      };
  };
  
  const saveObject = (base) => {
      return (object, requestOptions) => {
          return clientCommon.createWaitablePromise(saveObjects(base)([object], requestOptions).then(response => {
              return {
                  objectID: response.objectIDs[0],
                  taskID: response.taskIDs[0],
              };
          }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const saveObjects = (base) => {
      return (objects, requestOptions) => {
          const { autoGenerateObjectIDIfNotExist, ...options } = requestOptions || {};
          const action = autoGenerateObjectIDIfNotExist
              ? BatchActionEnum.AddObject
              : BatchActionEnum.UpdateObject;
          if (action === BatchActionEnum.UpdateObject) {
              // eslint-disable-next-line functional/no-loop-statement
              for (const object of objects) {
                  if (object.objectID === undefined) {
                      return clientCommon.createWaitablePromise(Promise.reject(createMissingObjectIDError()));
                  }
              }
          }
          return chunkedBatch(base)(objects, action, options);
      };
  };
  
  const saveRule = (base) => {
      return (rule, requestOptions) => {
          return saveRules(base)([rule], requestOptions);
      };
  };
  
  const saveRules = (base) => {
      return (rules, requestOptions) => {
          const { forwardToReplicas, clearExistingRules, ...options } = requestOptions || {};
          const mappedRequestOptions = transporter.createMappedRequestOptions(options);
          if (forwardToReplicas) {
              mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
          }
          if (clearExistingRules) {
              mappedRequestOptions.queryParameters.clearExistingRules = 1; // eslint-disable-line functional/immutable-data
          }
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/rules/batch', base.indexName),
              data: rules,
          }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const saveSynonym = (base) => {
      return (synonym, requestOptions) => {
          return saveSynonyms(base)([synonym], requestOptions);
      };
  };
  
  const saveSynonyms = (base) => {
      return (synonyms, requestOptions) => {
          const { forwardToReplicas, clearExistingSynonyms, replaceExistingSynonyms, ...options } = requestOptions || {};
          const mappedRequestOptions = transporter.createMappedRequestOptions(options);
          if (forwardToReplicas) {
              mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
          }
          if (replaceExistingSynonyms || clearExistingSynonyms) {
              mappedRequestOptions.queryParameters.replaceExistingSynonyms = 1; // eslint-disable-line functional/immutable-data
          }
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/synonyms/batch', base.indexName),
              data: synonyms,
          }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const search = (base) => {
      return (query, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/query', base.indexName),
              data: {
                  query,
              },
              cacheable: true,
          }, requestOptions);
      };
  };
  
  const searchForFacetValues = (base) => {
      return (facetName, facetQuery, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/facets/%s/query', base.indexName, facetName),
              data: {
                  facetQuery,
              },
              cacheable: true,
          }, requestOptions);
      };
  };
  
  const searchRules = (base) => {
      return (query, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/rules/search', base.indexName),
              data: {
                  query,
              },
          }, requestOptions);
      };
  };
  
  const searchSynonyms = (base) => {
      return (query, requestOptions) => {
          return base.transporter.read({
              method: requesterCommon.MethodEnum.Post,
              path: clientCommon.encode('1/indexes/%s/synonyms/search', base.indexName),
              data: {
                  query,
              },
          }, requestOptions);
      };
  };
  
  const setSettings = (base) => {
      return (settings, requestOptions) => {
          const { forwardToReplicas, ...options } = requestOptions || {};
          const mappedRequestOptions = transporter.createMappedRequestOptions(options);
          if (forwardToReplicas) {
              mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
          }
          return clientCommon.createWaitablePromise(base.transporter.write({
              method: requesterCommon.MethodEnum.Put,
              path: clientCommon.encode('1/indexes/%s/settings', base.indexName),
              data: settings,
          }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
      };
  };
  
  const waitTask = (base) => {
      return (taskID, requestOptions) => {
          return clientCommon.createRetryablePromise(retry => {
              return getTask(base)(taskID, requestOptions).then(response => {
                  return response.status !== 'published' ? retry() : undefined;
              });
          });
      };
  };
  
  const ApiKeyACLEnum = {
      AddObject: 'addObject',
      Analytics: 'analytics',
      Browser: 'browse',
      DeleteIndex: 'deleteIndex',
      DeleteObject: 'deleteObject',
      EditSettings: 'editSettings',
      ListIndexes: 'listIndexes',
      Logs: 'logs',
      Recommendation: 'recommendation',
      Search: 'search',
      SeeUnretrievableAttributes: 'seeUnretrievableAttributes',
      Settings: 'settings',
      Usage: 'usage',
  };
  
  const BatchActionEnum = {
      AddObject: 'addObject',
      UpdateObject: 'updateObject',
      PartialUpdateObject: 'partialUpdateObject',
      PartialUpdateObjectNoCreate: 'partialUpdateObjectNoCreate',
      DeleteObject: 'deleteObject',
      DeleteIndex: 'delete',
      ClearIndex: 'clear',
  };
  
  const ScopeEnum = {
      Settings: 'settings',
      Synonyms: 'synonyms',
      Rules: 'rules',
  };
  
  const StrategyEnum = {
      None: 'none',
      StopIfEnoughMatches: 'stopIfEnoughMatches',
  };
  
  const SynonymEnum = {
      Synonym: 'synonym',
      OneWaySynonym: 'oneWaySynonym',
      AltCorrection1: 'altCorrection1',
      AltCorrection2: 'altCorrection2',
      Placeholder: 'placeholder',
  };
  
  exports.ApiKeyACLEnum = ApiKeyACLEnum;
  exports.BatchActionEnum = BatchActionEnum;
  exports.ScopeEnum = ScopeEnum;
  exports.StrategyEnum = StrategyEnum;
  exports.SynonymEnum = SynonymEnum;
  exports.addApiKey = addApiKey;
  exports.assignUserID = assignUserID;
  exports.assignUserIDs = assignUserIDs;
  exports.batch = batch;
  exports.browseObjects = browseObjects;
  exports.browseRules = browseRules;
  exports.browseSynonyms = browseSynonyms;
  exports.chunkedBatch = chunkedBatch;
  exports.clearDictionaryEntries = clearDictionaryEntries;
  exports.clearObjects = clearObjects;
  exports.clearRules = clearRules;
  exports.clearSynonyms = clearSynonyms;
  exports.copyIndex = copyIndex;
  exports.copyRules = copyRules;
  exports.copySettings = copySettings;
  exports.copySynonyms = copySynonyms;
  exports.createBrowsablePromise = createBrowsablePromise;
  exports.createMissingObjectIDError = createMissingObjectIDError;
  exports.createObjectNotFoundError = createObjectNotFoundError;
  exports.createSearchClient = createSearchClient;
  exports.createValidUntilNotFoundError = createValidUntilNotFoundError;
  exports.deleteApiKey = deleteApiKey;
  exports.deleteBy = deleteBy;
  exports.deleteDictionaryEntries = deleteDictionaryEntries;
  exports.deleteIndex = deleteIndex;
  exports.deleteObject = deleteObject;
  exports.deleteObjects = deleteObjects;
  exports.deleteRule = deleteRule;
  exports.deleteSynonym = deleteSynonym;
  exports.exists = exists;
  exports.findAnswers = findAnswers;
  exports.findObject = findObject;
  exports.generateSecuredApiKey = generateSecuredApiKey;
  exports.getApiKey = getApiKey;
  exports.getAppTask = getAppTask;
  exports.getDictionarySettings = getDictionarySettings;
  exports.getLogs = getLogs;
  exports.getObject = getObject;
  exports.getObjectPosition = getObjectPosition;
  exports.getObjects = getObjects;
  exports.getRule = getRule;
  exports.getSecuredApiKeyRemainingValidity = getSecuredApiKeyRemainingValidity;
  exports.getSettings = getSettings;
  exports.getSynonym = getSynonym;
  exports.getTask = getTask;
  exports.getTopUserIDs = getTopUserIDs;
  exports.getUserID = getUserID;
  exports.hasPendingMappings = hasPendingMappings;
  exports.initIndex = initIndex;
  exports.listApiKeys = listApiKeys;
  exports.listClusters = listClusters;
  exports.listIndices = listIndices;
  exports.listUserIDs = listUserIDs;
  exports.moveIndex = moveIndex;
  exports.multipleBatch = multipleBatch;
  exports.multipleGetObjects = multipleGetObjects;
  exports.multipleQueries = multipleQueries;
  exports.multipleSearchForFacetValues = multipleSearchForFacetValues;
  exports.partialUpdateObject = partialUpdateObject;
  exports.partialUpdateObjects = partialUpdateObjects;
  exports.removeUserID = removeUserID;
  exports.replaceAllObjects = replaceAllObjects;
  exports.replaceAllRules = replaceAllRules;
  exports.replaceAllSynonyms = replaceAllSynonyms;
  exports.replaceDictionaryEntries = replaceDictionaryEntries;
  exports.restoreApiKey = restoreApiKey;
  exports.saveDictionaryEntries = saveDictionaryEntries;
  exports.saveObject = saveObject;
  exports.saveObjects = saveObjects;
  exports.saveRule = saveRule;
  exports.saveRules = saveRules;
  exports.saveSynonym = saveSynonym;
  exports.saveSynonyms = saveSynonyms;
  exports.search = search;
  exports.searchDictionaryEntries = searchDictionaryEntries;
  exports.searchForFacetValues = searchForFacetValues;
  exports.searchRules = searchRules;
  exports.searchSynonyms = searchSynonyms;
  exports.searchUserIDs = searchUserIDs;
  exports.setDictionarySettings = setDictionarySettings;
  exports.setSettings = setSettings;
  exports.updateApiKey = updateApiKey;
  exports.waitAppTask = waitAppTask;
  exports.waitTask = waitTask;
  