1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221 |
- function createBrowserLocalStorageCache(options) {
- const namespaceKey = `algoliasearch-client-js-${options.key}`;
- // eslint-disable-next-line functional/no-let
- let storage;
- const getStorage = () => {
- if (storage === undefined) {
- storage = options.localStorage || window.localStorage;
- }
- return storage;
- };
- const getNamespace = () => {
- return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
- };
- return {
- get(key, defaultValue, events = {
- miss: () => Promise.resolve(),
- }) {
- return Promise.resolve()
- .then(() => {
- const keyAsString = JSON.stringify(key);
- const value = getNamespace()[keyAsString];
- return Promise.all([value || defaultValue(), value !== undefined]);
- })
- .then(([value, exists]) => {
- return Promise.all([value, exists || events.miss(value)]);
- })
- .then(([value]) => value);
- },
- set(key, value) {
- return Promise.resolve().then(() => {
- const namespace = getNamespace();
- // eslint-disable-next-line functional/immutable-data
- namespace[JSON.stringify(key)] = value;
- getStorage().setItem(namespaceKey, JSON.stringify(namespace));
- return value;
- });
- },
- delete(key) {
- return Promise.resolve().then(() => {
- const namespace = getNamespace();
- // eslint-disable-next-line functional/immutable-data
- delete namespace[JSON.stringify(key)];
- getStorage().setItem(namespaceKey, JSON.stringify(namespace));
- });
- },
- clear() {
- return Promise.resolve().then(() => {
- getStorage().removeItem(namespaceKey);
- });
- },
- };
- }
- // @todo Add logger on options to debug when caches go wrong.
- function createFallbackableCache(options) {
- const caches = [...options.caches];
- const current = caches.shift(); // eslint-disable-line functional/immutable-data
- if (current === undefined) {
- return createNullCache();
- }
- return {
- get(key, defaultValue, events = {
- miss: () => Promise.resolve(),
- }) {
- return current.get(key, defaultValue, events).catch(() => {
- return createFallbackableCache({ caches }).get(key, defaultValue, events);
- });
- },
- set(key, value) {
- return current.set(key, value).catch(() => {
- return createFallbackableCache({ caches }).set(key, value);
- });
- },
- delete(key) {
- return current.delete(key).catch(() => {
- return createFallbackableCache({ caches }).delete(key);
- });
- },
- clear() {
- return current.clear().catch(() => {
- return createFallbackableCache({ caches }).clear();
- });
- },
- };
- }
- function createNullCache() {
- return {
- get(_key, defaultValue, events = {
- miss: () => Promise.resolve(),
- }) {
- const value = defaultValue();
- return value
- .then(result => Promise.all([result, events.miss(result)]))
- .then(([result]) => result);
- },
- set(_key, value) {
- return Promise.resolve(value);
- },
- delete(_key) {
- return Promise.resolve();
- },
- clear() {
- return Promise.resolve();
- },
- };
- }
- function createInMemoryCache(options = { serializable: true }) {
- // eslint-disable-next-line functional/no-let
- let cache = {};
- return {
- get(key, defaultValue, events = {
- miss: () => Promise.resolve(),
- }) {
- const keyAsString = JSON.stringify(key);
- if (keyAsString in cache) {
- return Promise.resolve(options.serializable ? JSON.parse(cache[keyAsString]) : cache[keyAsString]);
- }
- const promise = defaultValue();
- const miss = (events && events.miss) || (() => Promise.resolve());
- return promise.then((value) => miss(value)).then(() => promise);
- },
- set(key, value) {
- // eslint-disable-next-line functional/immutable-data
- cache[JSON.stringify(key)] = options.serializable ? JSON.stringify(value) : value;
- return Promise.resolve(value);
- },
- delete(key) {
- // eslint-disable-next-line functional/immutable-data
- delete cache[JSON.stringify(key)];
- return Promise.resolve();
- },
- clear() {
- cache = {};
- return Promise.resolve();
- },
- };
- }
- function createAuth(authMode, appId, apiKey) {
- const credentials = {
- 'x-algolia-api-key': apiKey,
- 'x-algolia-application-id': appId,
- };
- return {
- headers() {
- return authMode === AuthMode.WithinHeaders ? credentials : {};
- },
- queryParameters() {
- return authMode === AuthMode.WithinQueryParameters ? credentials : {};
- },
- };
- }
- function createRetryablePromise(callback) {
- let retriesCount = 0; // eslint-disable-line functional/no-let
- const retry = () => {
- retriesCount++;
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve(callback(retry));
- }, Math.min(100 * retriesCount, 1000));
- });
- };
- return callback(retry);
- }
- function createWaitablePromise(promise, wait = (_response, _requestOptions) => {
- return Promise.resolve();
- }) {
- // eslint-disable-next-line functional/immutable-data
- return Object.assign(promise, {
- wait(requestOptions) {
- return createWaitablePromise(promise
- .then(response => Promise.all([wait(response, requestOptions), response]))
- .then(promiseResults => promiseResults[1]));
- },
- });
- }
- // eslint-disable-next-line functional/prefer-readonly-type
- function shuffle(array) {
- let c = array.length - 1; // eslint-disable-line functional/no-let
- // eslint-disable-next-line functional/no-loop-statement
- for (c; c > 0; c--) {
- const b = Math.floor(Math.random() * (c + 1));
- const a = array[c];
- array[c] = array[b]; // eslint-disable-line functional/immutable-data, no-param-reassign
- array[b] = a; // eslint-disable-line functional/immutable-data, no-param-reassign
- }
- return array;
- }
- function addMethods(base, methods) {
- if (!methods) {
- return base;
- }
- Object.keys(methods).forEach(key => {
- // eslint-disable-next-line functional/immutable-data, no-param-reassign
- base[key] = methods[key](base);
- });
- return base;
- }
- function encode(format, ...args) {
- // eslint-disable-next-line functional/no-let
- let i = 0;
- return format.replace(/%s/g, () => encodeURIComponent(args[i++]));
- }
- const version = '4.13.0';
- const AuthMode = {
- /**
- * If auth credentials should be in query parameters.
- */
- WithinQueryParameters: 0,
- /**
- * If auth credentials should be in headers.
- */
- WithinHeaders: 1,
- };
- function createMappedRequestOptions(requestOptions, timeout) {
- const options = requestOptions || {};
- const data = options.data || {};
- Object.keys(options).forEach(key => {
- if (['timeout', 'headers', 'queryParameters', 'data', 'cacheable'].indexOf(key) === -1) {
- data[key] = options[key]; // eslint-disable-line functional/immutable-data
- }
- });
- return {
- data: Object.entries(data).length > 0 ? data : undefined,
- timeout: options.timeout || timeout,
- headers: options.headers || {},
- queryParameters: options.queryParameters || {},
- cacheable: options.cacheable,
- };
- }
- const CallEnum = {
- /**
- * If the host is read only.
- */
- Read: 1,
- /**
- * If the host is write only.
- */
- Write: 2,
- /**
- * If the host is both read and write.
- */
- Any: 3,
- };
- const HostStatusEnum = {
- Up: 1,
- Down: 2,
- Timeouted: 3,
- };
- // By default, API Clients at Algolia have expiration delay
- // of 5 mins. In the JavaScript client, we have 2 mins.
- const EXPIRATION_DELAY = 2 * 60 * 1000;
- function createStatefulHost(host, status = HostStatusEnum.Up) {
- return {
- ...host,
- status,
- lastUpdate: Date.now(),
- };
- }
- function isStatefulHostUp(host) {
- return host.status === HostStatusEnum.Up || Date.now() - host.lastUpdate > EXPIRATION_DELAY;
- }
- function isStatefulHostTimeouted(host) {
- return (host.status === HostStatusEnum.Timeouted && Date.now() - host.lastUpdate <= EXPIRATION_DELAY);
- }
- function createStatelessHost(options) {
- if (typeof options === 'string') {
- return {
- protocol: 'https',
- url: options,
- accept: CallEnum.Any,
- };
- }
- return {
- protocol: options.protocol || 'https',
- url: options.url,
- accept: options.accept || CallEnum.Any,
- };
- }
- const MethodEnum = {
- Delete: 'DELETE',
- Get: 'GET',
- Post: 'POST',
- Put: 'PUT',
- };
- function createRetryableOptions(hostsCache, statelessHosts) {
- return Promise.all(statelessHosts.map(statelessHost => {
- return hostsCache.get(statelessHost, () => {
- return Promise.resolve(createStatefulHost(statelessHost));
- });
- })).then(statefulHosts => {
- const hostsUp = statefulHosts.filter(host => isStatefulHostUp(host));
- const hostsTimeouted = statefulHosts.filter(host => isStatefulHostTimeouted(host));
- /**
- * Note, we put the hosts that previously timeouted on the end of the list.
- */
- const hostsAvailable = [...hostsUp, ...hostsTimeouted];
- const statelessHostsAvailable = hostsAvailable.length > 0
- ? hostsAvailable.map(host => createStatelessHost(host))
- : statelessHosts;
- return {
- getTimeout(timeoutsCount, baseTimeout) {
- /**
- * Imagine that you have 4 hosts, if timeouts will increase
- * on the following way: 1 (timeouted) > 4 (timeouted) > 5 (200)
- *
- * Note that, the very next request, we start from the previous timeout
- *
- * 5 (timeouted) > 6 (timeouted) > 7 ...
- *
- * This strategy may need to be reviewed, but is the strategy on the our
- * current v3 version.
- */
- const timeoutMultiplier = hostsTimeouted.length === 0 && timeoutsCount === 0
- ? 1
- : hostsTimeouted.length + 3 + timeoutsCount;
- return timeoutMultiplier * baseTimeout;
- },
- statelessHosts: statelessHostsAvailable,
- };
- });
- }
- const isNetworkError = ({ isTimedOut, status }) => {
- return !isTimedOut && ~~status === 0;
- };
- const isRetryable = (response) => {
- const status = response.status;
- const isTimedOut = response.isTimedOut;
- return (isTimedOut || isNetworkError(response) || (~~(status / 100) !== 2 && ~~(status / 100) !== 4));
- };
- const isSuccess = ({ status }) => {
- return ~~(status / 100) === 2;
- };
- const retryDecision = (response, outcomes) => {
- if (isRetryable(response)) {
- return outcomes.onRetry(response);
- }
- if (isSuccess(response)) {
- return outcomes.onSuccess(response);
- }
- return outcomes.onFail(response);
- };
- function retryableRequest(transporter, statelessHosts, request, requestOptions) {
- const stackTrace = []; // eslint-disable-line functional/prefer-readonly-type
- /**
- * First we prepare the payload that do not depend from hosts.
- */
- const data = serializeData(request, requestOptions);
- const headers = serializeHeaders(transporter, requestOptions);
- const method = request.method;
- // On `GET`, the data is proxied to query parameters.
- const dataQueryParameters = request.method !== MethodEnum.Get
- ? {}
- : {
- ...request.data,
- ...requestOptions.data,
- };
- const queryParameters = {
- 'x-algolia-agent': transporter.userAgent.value,
- ...transporter.queryParameters,
- ...dataQueryParameters,
- ...requestOptions.queryParameters,
- };
- let timeoutsCount = 0; // eslint-disable-line functional/no-let
- const retry = (hosts, // eslint-disable-line functional/prefer-readonly-type
- getTimeout) => {
- /**
- * We iterate on each host, until there is no host left.
- */
- const host = hosts.pop(); // eslint-disable-line functional/immutable-data
- if (host === undefined) {
- throw createRetryError(stackTraceWithoutCredentials(stackTrace));
- }
- const payload = {
- data,
- headers,
- method,
- url: serializeUrl(host, request.path, queryParameters),
- connectTimeout: getTimeout(timeoutsCount, transporter.timeouts.connect),
- responseTimeout: getTimeout(timeoutsCount, requestOptions.timeout),
- };
- /**
- * The stackFrame is pushed to the stackTrace so we
- * can have information about onRetry and onFailure
- * decisions.
- */
- const pushToStackTrace = (response) => {
- const stackFrame = {
- request: payload,
- response,
- host,
- triesLeft: hosts.length,
- };
- // eslint-disable-next-line functional/immutable-data
- stackTrace.push(stackFrame);
- return stackFrame;
- };
- const decisions = {
- onSuccess: response => deserializeSuccess(response),
- onRetry(response) {
- const stackFrame = pushToStackTrace(response);
- /**
- * If response is a timeout, we increaset the number of
- * timeouts so we can increase the timeout later.
- */
- if (response.isTimedOut) {
- timeoutsCount++;
- }
- return Promise.all([
- /**
- * Failures are individually send the logger, allowing
- * the end user to debug / store stack frames even
- * when a retry error does not happen.
- */
- transporter.logger.info('Retryable failure', stackFrameWithoutCredentials(stackFrame)),
- /**
- * We also store the state of the host in failure cases. If the host, is
- * down it will remain down for the next 2 minutes. In a timeout situation,
- * this host will be added end of the list of hosts on the next request.
- */
- transporter.hostsCache.set(host, createStatefulHost(host, response.isTimedOut ? HostStatusEnum.Timeouted : HostStatusEnum.Down)),
- ]).then(() => retry(hosts, getTimeout));
- },
- onFail(response) {
- pushToStackTrace(response);
- throw deserializeFailure(response, stackTraceWithoutCredentials(stackTrace));
- },
- };
- return transporter.requester.send(payload).then(response => {
- return retryDecision(response, decisions);
- });
- };
- /**
- * Finally, for each retryable host perform request until we got a non
- * retryable response. Some notes here:
- *
- * 1. The reverse here is applied so we can apply a `pop` later on => more performant.
- * 2. We also get from the retryable options a timeout multiplier that is tailored
- * for the current context.
- */
- return createRetryableOptions(transporter.hostsCache, statelessHosts).then(options => {
- return retry([...options.statelessHosts].reverse(), options.getTimeout);
- });
- }
- function createTransporter(options) {
- const { hostsCache, logger, requester, requestsCache, responsesCache, timeouts, userAgent, hosts, queryParameters, headers, } = options;
- const transporter = {
- hostsCache,
- logger,
- requester,
- requestsCache,
- responsesCache,
- timeouts,
- userAgent,
- headers,
- queryParameters,
- hosts: hosts.map(host => createStatelessHost(host)),
- read(request, requestOptions) {
- /**
- * First, we compute the user request options. Now, keep in mind,
- * that using request options the user is able to modified the intire
- * payload of the request. Such as headers, query parameters, and others.
- */
- const mappedRequestOptions = createMappedRequestOptions(requestOptions, transporter.timeouts.read);
- const createRetryableRequest = () => {
- /**
- * Then, we prepare a function factory that contains the construction of
- * the retryable request. At this point, we may *not* perform the actual
- * request. But we want to have the function factory ready.
- */
- return retryableRequest(transporter, transporter.hosts.filter(host => (host.accept & CallEnum.Read) !== 0), request, mappedRequestOptions);
- };
- /**
- * Once we have the function factory ready, we need to determine of the
- * request is "cacheable" - should be cached. Note that, once again,
- * the user can force this option.
- */
- const cacheable = mappedRequestOptions.cacheable !== undefined
- ? mappedRequestOptions.cacheable
- : request.cacheable;
- /**
- * If is not "cacheable", we immediatly trigger the retryable request, no
- * need to check cache implementations.
- */
- if (cacheable !== true) {
- return createRetryableRequest();
- }
- /**
- * If the request is "cacheable", we need to first compute the key to ask
- * the cache implementations if this request is on progress or if the
- * response already exists on the cache.
- */
- const key = {
- request,
- mappedRequestOptions,
- transporter: {
- queryParameters: transporter.queryParameters,
- headers: transporter.headers,
- },
- };
- /**
- * With the computed key, we first ask the responses cache
- * implemention if this request was been resolved before.
- */
- return transporter.responsesCache.get(key, () => {
- /**
- * If the request has never resolved before, we actually ask if there
- * is a current request with the same key on progress.
- */
- return transporter.requestsCache.get(key, () => {
- return (transporter.requestsCache
- /**
- * Finally, if there is no request in progress with the same key,
- * this `createRetryableRequest()` will actually trigger the
- * retryable request.
- */
- .set(key, createRetryableRequest())
- .then(response => Promise.all([transporter.requestsCache.delete(key), response]), err => Promise.all([transporter.requestsCache.delete(key), Promise.reject(err)]))
- .then(([_, response]) => response));
- });
- }, {
- /**
- * Of course, once we get this response back from the server, we
- * tell response cache to actually store the received response
- * to be used later.
- */
- miss: response => transporter.responsesCache.set(key, response),
- });
- },
- write(request, requestOptions) {
- /**
- * On write requests, no cache mechanisms are applied, and we
- * proxy the request immediately to the requester.
- */
- return retryableRequest(transporter, transporter.hosts.filter(host => (host.accept & CallEnum.Write) !== 0), request, createMappedRequestOptions(requestOptions, transporter.timeouts.write));
- },
- };
- return transporter;
- }
- function createUserAgent(version) {
- const userAgent = {
- value: `Algolia for JavaScript (${version})`,
- add(options) {
- const addedUserAgent = `; ${options.segment}${options.version !== undefined ? ` (${options.version})` : ''}`;
- if (userAgent.value.indexOf(addedUserAgent) === -1) {
- // eslint-disable-next-line functional/immutable-data
- userAgent.value = `${userAgent.value}${addedUserAgent}`;
- }
- return userAgent;
- },
- };
- return userAgent;
- }
- function deserializeSuccess(response) {
- // eslint-disable-next-line functional/no-try-statement
- try {
- return JSON.parse(response.content);
- }
- catch (e) {
- throw createDeserializationError(e.message, response);
- }
- }
- function deserializeFailure({ content, status }, stackFrame) {
- // eslint-disable-next-line functional/no-let
- let message = content;
- // eslint-disable-next-line functional/no-try-statement
- try {
- message = JSON.parse(content).message;
- }
- catch (e) {
- // ..
- }
- return createApiError(message, status, stackFrame);
- }
- function serializeUrl(host, path, queryParameters) {
- const queryParametersAsString = serializeQueryParameters(queryParameters);
- // eslint-disable-next-line functional/no-let
- let url = `${host.protocol}://${host.url}/${path.charAt(0) === '/' ? path.substr(1) : path}`;
- if (queryParametersAsString.length) {
- url += `?${queryParametersAsString}`;
- }
- return url;
- }
- function serializeQueryParameters(parameters) {
- const isObjectOrArray = (value) => Object.prototype.toString.call(value) === '[object Object]' ||
- Object.prototype.toString.call(value) === '[object Array]';
- return Object.keys(parameters)
- .map(key => encode('%s=%s', key, isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key]))
- .join('&');
- }
- function serializeData(request, requestOptions) {
- if (request.method === MethodEnum.Get ||
- (request.data === undefined && requestOptions.data === undefined)) {
- return undefined;
- }
- const data = Array.isArray(request.data)
- ? request.data
- : { ...request.data, ...requestOptions.data };
- return JSON.stringify(data);
- }
- function serializeHeaders(transporter, requestOptions) {
- const headers = {
- ...transporter.headers,
- ...requestOptions.headers,
- };
- const serializedHeaders = {};
- Object.keys(headers).forEach(header => {
- const value = headers[header];
- // @ts-ignore
- // eslint-disable-next-line functional/immutable-data
- serializedHeaders[header.toLowerCase()] = value;
- });
- return serializedHeaders;
- }
- function stackTraceWithoutCredentials(stackTrace) {
- return stackTrace.map(stackFrame => stackFrameWithoutCredentials(stackFrame));
- }
- function stackFrameWithoutCredentials(stackFrame) {
- const modifiedHeaders = stackFrame.request.headers['x-algolia-api-key']
- ? { 'x-algolia-api-key': '*****' }
- : {};
- return {
- ...stackFrame,
- request: {
- ...stackFrame.request,
- headers: {
- ...stackFrame.request.headers,
- ...modifiedHeaders,
- },
- },
- };
- }
- function createApiError(message, status, transporterStackTrace) {
- return {
- name: 'ApiError',
- message,
- status,
- transporterStackTrace,
- };
- }
- function createDeserializationError(message, response) {
- return {
- name: 'DeserializationError',
- message,
- response,
- };
- }
- function createRetryError(transporterStackTrace) {
- return {
- name: 'RetryError',
- message: 'Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.',
- transporterStackTrace,
- };
- }
- const createAnalyticsClient = options => {
- const region = options.region || 'us';
- const auth = createAuth(AuthMode.WithinHeaders, options.appId, options.apiKey);
- const transporter = createTransporter({
- hosts: [{ url: `analytics.${region}.algolia.com` }],
- ...options,
- headers: {
- ...auth.headers(),
- ...{ 'content-type': 'application/json' },
- ...options.headers,
- },
- queryParameters: {
- ...auth.queryParameters(),
- ...options.queryParameters,
- },
- });
- const appId = options.appId;
- return addMethods({ appId, transporter }, options.methods);
- };
- const addABTest = (base) => {
- return (abTest, requestOptions) => {
- return base.transporter.write({
- method: MethodEnum.Post,
- path: '2/abtests',
- data: abTest,
- }, requestOptions);
- };
- };
- const deleteABTest = (base) => {
- return (abTestID, requestOptions) => {
- return base.transporter.write({
- method: MethodEnum.Delete,
- path: encode('2/abtests/%s', abTestID),
- }, requestOptions);
- };
- };
- const getABTest = (base) => {
- return (abTestID, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: encode('2/abtests/%s', abTestID),
- }, requestOptions);
- };
- };
- const getABTests = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: '2/abtests',
- }, requestOptions);
- };
- };
- const stopABTest = (base) => {
- return (abTestID, requestOptions) => {
- return base.transporter.write({
- method: MethodEnum.Post,
- path: encode('2/abtests/%s/stop', abTestID),
- }, requestOptions);
- };
- };
- const createPersonalizationClient = options => {
- const region = options.region || 'us';
- const auth = createAuth(AuthMode.WithinHeaders, options.appId, options.apiKey);
- const transporter = createTransporter({
- hosts: [{ url: `personalization.${region}.algolia.com` }],
- ...options,
- headers: {
- ...auth.headers(),
- ...{ 'content-type': 'application/json' },
- ...options.headers,
- },
- queryParameters: {
- ...auth.queryParameters(),
- ...options.queryParameters,
- },
- });
- return addMethods({ appId: options.appId, transporter }, options.methods);
- };
- const getPersonalizationStrategy = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: '1/strategies/personalization',
- }, requestOptions);
- };
- };
- const setPersonalizationStrategy = (base) => {
- return (personalizationStrategy, requestOptions) => {
- return base.transporter.write({
- method: MethodEnum.Post,
- path: '1/strategies/personalization',
- data: personalizationStrategy,
- }, requestOptions);
- };
- };
- 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 = createAuth(options.authMode !== undefined ? options.authMode : AuthMode.WithinHeaders, appId, options.apiKey);
- const transporter = createTransporter({
- hosts: [
- { url: `${appId}-dsn.algolia.net`, accept: CallEnum.Read },
- { url: `${appId}.algolia.net`, accept: CallEnum.Write },
- ].concat(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,
- appId,
- addAlgoliaAgent(segment, version) {
- transporter.userAgent.add({ segment, version });
- },
- clearCache() {
- return Promise.all([
- transporter.requestsCache.clear(),
- transporter.responsesCache.clear(),
- ]).then(() => undefined);
- },
- };
- return 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.',
- };
- }
- const addApiKey = (base) => {
- return (acl, requestOptions) => {
- const { queryParameters, ...options } = requestOptions || {};
- const data = {
- acl,
- ...(queryParameters !== undefined ? { queryParameters } : {}),
- };
- const wait = (response, waitRequestOptions) => {
- return createRetryablePromise(retry => {
- return getApiKey(base)(response.key, waitRequestOptions).catch((apiError) => {
- if (apiError.status !== 404) {
- throw apiError;
- }
- return retry();
- });
- });
- };
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: '1/keys',
- data,
- }, options), wait);
- };
- };
- const assignUserID = (base) => {
- return (userID, clusterName, requestOptions) => {
- const mappedRequestOptions = createMappedRequestOptions(requestOptions);
- // eslint-disable-next-line functional/immutable-data
- mappedRequestOptions.queryParameters['X-Algolia-User-ID'] = userID;
- return base.transporter.write({
- method: MethodEnum.Post,
- path: '1/clusters/mapping',
- data: { cluster: clusterName },
- }, mappedRequestOptions);
- };
- };
- const assignUserIDs = (base) => {
- return (userIDs, clusterName, requestOptions) => {
- return base.transporter.write({
- method: MethodEnum.Post,
- path: '1/clusters/mapping/batch',
- data: {
- users: userIDs,
- cluster: clusterName,
- },
- }, requestOptions);
- };
- };
- const clearDictionaryEntries = (base) => {
- return (dictionary, requestOptions) => {
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 customRequest = (base) => {
- return (request, requestOptions) => {
- if (request.method === MethodEnum.Get) {
- return base.transporter.read(request, requestOptions);
- }
- return base.transporter.write(request, requestOptions);
- };
- };
- const deleteApiKey = (base) => {
- return (apiKey, requestOptions) => {
- const wait = (_, waitRequestOptions) => {
- return createRetryablePromise(retry => {
- return getApiKey(base)(apiKey, waitRequestOptions)
- .then(retry)
- .catch((apiError) => {
- if (apiError.status !== 404) {
- throw apiError;
- }
- });
- });
- };
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Delete,
- path: encode('1/keys/%s', apiKey),
- }, requestOptions), wait);
- };
- };
- const deleteDictionaryEntries = (base) => {
- return (dictionary, objectIDs, requestOptions) => {
- const requests = objectIDs.map(objectID => ({
- action: 'deleteEntry',
- body: { objectID },
- }));
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: encode('/1/dictionaries/%s/batch', dictionary),
- data: { clearExistingDictionaryEntries: false, requests },
- }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
- };
- };
- const getApiKey = (base) => {
- return (apiKey, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: encode('1/keys/%s', apiKey),
- }, requestOptions);
- };
- };
- const getAppTask = (base) => {
- return (taskID, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: encode('1/task/%s', taskID.toString()),
- }, requestOptions);
- };
- };
- const getDictionarySettings = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: '/1/dictionaries/*/settings',
- }, requestOptions);
- };
- };
- const getLogs = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: '1/logs',
- }, requestOptions);
- };
- };
- const getTopUserIDs = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: '1/clusters/mapping/top',
- }, requestOptions);
- };
- };
- const getUserID = (base) => {
- return (userID, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: 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: MethodEnum.Get,
- path: '1/clusters/mapping/pending',
- }, options);
- };
- };
- const initIndex = (base) => {
- return (indexName, options = {}) => {
- const searchIndex = {
- transporter: base.transporter,
- appId: base.appId,
- indexName,
- };
- return addMethods(searchIndex, options.methods);
- };
- };
- const listApiKeys = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: '1/keys',
- }, requestOptions);
- };
- };
- const listClusters = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: '1/clusters',
- }, requestOptions);
- };
- };
- const listIndices = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: '1/indexes',
- }, requestOptions);
- };
- };
- const listUserIDs = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: 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 createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: '1/indexes/*/batch',
- data: {
- requests,
- },
- }, requestOptions), wait);
- };
- };
- const multipleGetObjects = (base) => {
- return (requests, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Post,
- path: '1/indexes/*/objects',
- data: {
- requests,
- },
- }, requestOptions);
- };
- };
- const multipleQueries = (base) => {
- return (queries, requestOptions) => {
- const requests = queries.map(query => {
- return {
- ...query,
- params: serializeQueryParameters(query.params || {}),
- };
- });
- return base.transporter.read({
- method: 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 = createMappedRequestOptions(requestOptions);
- // eslint-disable-next-line functional/immutable-data
- mappedRequestOptions.queryParameters['X-Algolia-User-ID'] = userID;
- return base.transporter.write({
- method: MethodEnum.Delete,
- path: '1/clusters/mapping',
- }, mappedRequestOptions);
- };
- };
- const replaceDictionaryEntries = (base) => {
- return (dictionary, entries, requestOptions) => {
- const requests = entries.map(entry => ({
- action: 'addEntry',
- body: entry,
- }));
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 createRetryablePromise(retry => {
- return getApiKey(base)(apiKey, waitRequestOptions).catch((apiError) => {
- if (apiError.status !== 404) {
- throw apiError;
- }
- return retry();
- });
- });
- };
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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: MethodEnum.Post,
- path: encode('/1/dictionaries/%s/search', dictionary),
- data: {
- query,
- },
- cacheable: true,
- }, requestOptions);
- };
- };
- const searchUserIDs = (base) => {
- return (query, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Post,
- path: '1/clusters/mapping/search',
- data: {
- query,
- },
- }, requestOptions);
- };
- };
- const setDictionarySettings = (base) => {
- return (settings, requestOptions) => {
- return createWaitablePromise(base.transporter.write({
- method: 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) => createRetryablePromise(retry => {
- return getApiKey(base)(apiKey, waitRequestOptions).then(getApiKeyResponse => {
- return hasChanged(getApiKeyResponse) ? Promise.resolve() : retry();
- });
- });
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Put,
- path: encode('1/keys/%s', apiKey),
- data,
- }, options), wait);
- };
- };
- const waitAppTask = (base) => {
- return (taskID, requestOptions) => {
- return 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 createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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: MethodEnum.Post,
- path: 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 createWaitablePromise(forEachBatch(), (chunkedBatchResponse, waitRequestOptions) => {
- return Promise.all(chunkedBatchResponse.taskIDs.map(taskID => {
- return waitTask(base)(taskID, waitRequestOptions);
- }));
- });
- };
- };
- const clearObjects = (base) => {
- return (requestOptions) => {
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 = createMappedRequestOptions(options);
- if (forwardToReplicas) {
- mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
- }
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 = createMappedRequestOptions(options);
- if (forwardToReplicas) {
- mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
- }
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: encode('1/indexes/%s/synonyms/clear', base.indexName),
- }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
- };
- };
- const deleteBy = (base) => {
- return (filters, requestOptions) => {
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: encode('1/indexes/%s/deleteByQuery', base.indexName),
- data: filters,
- }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
- };
- };
- const deleteIndex = (base) => {
- return (requestOptions) => {
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Delete,
- path: encode('1/indexes/%s', base.indexName),
- }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
- };
- };
- const deleteObject = (base) => {
- return (objectID, requestOptions) => {
- return 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 = createMappedRequestOptions(options);
- if (forwardToReplicas) {
- mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
- }
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Delete,
- path: 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 = createMappedRequestOptions(options);
- if (forwardToReplicas) {
- mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
- }
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Delete,
- path: 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: MethodEnum.Post,
- path: 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: MethodEnum.Get,
- path: 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: MethodEnum.Post,
- path: '1/indexes/*/objects',
- data: {
- requests,
- },
- }, options);
- };
- };
- const getRule = (base) => {
- return (objectID, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: encode('1/indexes/%s/rules/%s', base.indexName, objectID),
- }, requestOptions);
- };
- };
- const getSettings = (base) => {
- return (requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: encode('1/indexes/%s/settings', base.indexName),
- data: {
- getVersion: 2,
- },
- }, requestOptions);
- };
- };
- const getSynonym = (base) => {
- return (objectID, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: encode(`1/indexes/%s/synonyms/%s`, base.indexName, objectID),
- }, requestOptions);
- };
- };
- const getTask = (base) => {
- return (taskID, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Get,
- path: encode('1/indexes/%s/task/%s', base.indexName, taskID.toString()),
- }, requestOptions);
- };
- };
- const partialUpdateObject = (base) => {
- return (object, requestOptions) => {
- return 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 createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 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 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 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 = 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 createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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 = 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 createWaitablePromise(base.transporter.write({
- method: MethodEnum.Post,
- path: 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: MethodEnum.Post,
- path: encode('1/indexes/%s/query', base.indexName),
- data: {
- query,
- },
- cacheable: true,
- }, requestOptions);
- };
- };
- const searchForFacetValues = (base) => {
- return (facetName, facetQuery, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Post,
- path: 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: MethodEnum.Post,
- path: encode('1/indexes/%s/rules/search', base.indexName),
- data: {
- query,
- },
- }, requestOptions);
- };
- };
- const searchSynonyms = (base) => {
- return (query, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Post,
- path: encode('1/indexes/%s/synonyms/search', base.indexName),
- data: {
- query,
- },
- }, requestOptions);
- };
- };
- const setSettings = (base) => {
- return (settings, requestOptions) => {
- const { forwardToReplicas, ...options } = requestOptions || {};
- const mappedRequestOptions = createMappedRequestOptions(options);
- if (forwardToReplicas) {
- mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
- }
- return createWaitablePromise(base.transporter.write({
- method: MethodEnum.Put,
- path: encode('1/indexes/%s/settings', base.indexName),
- data: settings,
- }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
- };
- };
- const waitTask = (base) => {
- return (taskID, requestOptions) => {
- return createRetryablePromise(retry => {
- return getTask(base)(taskID, requestOptions).then(response => {
- return response.status !== 'published' ? retry() : undefined;
- });
- });
- };
- };
- 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 LogLevelEnum = {
- Debug: 1,
- Info: 2,
- Error: 3,
- };
- /* eslint no-console: 0 */
- function createConsoleLogger(logLevel) {
- return {
- debug(message, args) {
- if (LogLevelEnum.Debug >= logLevel) {
- console.debug(message, args);
- }
- return Promise.resolve();
- },
- info(message, args) {
- if (LogLevelEnum.Info >= logLevel) {
- console.info(message, args);
- }
- return Promise.resolve();
- },
- error(message, args) {
- console.error(message, args);
- return Promise.resolve();
- },
- };
- }
- function createBrowserXhrRequester() {
- return {
- send(request) {
- return new Promise((resolve) => {
- const baseRequester = new XMLHttpRequest();
- baseRequester.open(request.method, request.url, true);
- Object.keys(request.headers).forEach(key => baseRequester.setRequestHeader(key, request.headers[key]));
- const createTimeout = (timeout, content) => {
- return setTimeout(() => {
- baseRequester.abort();
- resolve({
- status: 0,
- content,
- isTimedOut: true,
- });
- }, timeout * 1000);
- };
- const connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout');
- // eslint-disable-next-line functional/no-let
- let responseTimeout;
- // eslint-disable-next-line functional/immutable-data
- baseRequester.onreadystatechange = () => {
- if (baseRequester.readyState > baseRequester.OPENED && responseTimeout === undefined) {
- clearTimeout(connectTimeout);
- responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout');
- }
- };
- // eslint-disable-next-line functional/immutable-data
- baseRequester.onerror = () => {
- // istanbul ignore next
- if (baseRequester.status === 0) {
- clearTimeout(connectTimeout);
- clearTimeout(responseTimeout);
- resolve({
- content: baseRequester.responseText || 'Network request failed',
- status: baseRequester.status,
- isTimedOut: false,
- });
- }
- };
- // eslint-disable-next-line functional/immutable-data
- baseRequester.onload = () => {
- clearTimeout(connectTimeout);
- clearTimeout(responseTimeout);
- resolve({
- content: baseRequester.responseText,
- status: baseRequester.status,
- isTimedOut: false,
- });
- };
- baseRequester.send(request.data);
- });
- },
- };
- }
- function algoliasearch(appId, apiKey, options) {
- const commonOptions = {
- appId,
- apiKey,
- timeouts: {
- connect: 1,
- read: 2,
- write: 30,
- },
- requester: createBrowserXhrRequester(),
- logger: createConsoleLogger(LogLevelEnum.Error),
- responsesCache: createInMemoryCache(),
- requestsCache: createInMemoryCache({ serializable: false }),
- hostsCache: createFallbackableCache({
- caches: [
- createBrowserLocalStorageCache({ key: `${version}-${appId}` }),
- createInMemoryCache(),
- ],
- }),
- userAgent: createUserAgent(version).add({ segment: 'Browser' }),
- };
- const searchClientOptions = { ...commonOptions, ...options };
- const initPersonalization = () => (clientOptions) => {
- return createPersonalizationClient({
- ...commonOptions,
- ...clientOptions,
- methods: {
- getPersonalizationStrategy,
- setPersonalizationStrategy,
- },
- });
- };
- return createSearchClient({
- ...searchClientOptions,
- methods: {
- search: multipleQueries,
- searchForFacetValues: multipleSearchForFacetValues,
- multipleBatch,
- multipleGetObjects,
- multipleQueries,
- copyIndex,
- copySettings,
- copySynonyms,
- copyRules,
- moveIndex,
- listIndices,
- getLogs,
- listClusters,
- multipleSearchForFacetValues,
- getApiKey,
- addApiKey,
- listApiKeys,
- updateApiKey,
- deleteApiKey,
- restoreApiKey,
- assignUserID,
- assignUserIDs,
- getUserID,
- searchUserIDs,
- listUserIDs,
- getTopUserIDs,
- removeUserID,
- hasPendingMappings,
- clearDictionaryEntries,
- deleteDictionaryEntries,
- getDictionarySettings,
- getAppTask,
- replaceDictionaryEntries,
- saveDictionaryEntries,
- searchDictionaryEntries,
- setDictionarySettings,
- waitAppTask,
- customRequest,
- initIndex: base => (indexName) => {
- return initIndex(base)(indexName, {
- methods: {
- batch,
- delete: deleteIndex,
- findAnswers,
- getObject,
- getObjects,
- saveObject,
- saveObjects,
- search,
- searchForFacetValues,
- waitTask,
- setSettings,
- getSettings,
- partialUpdateObject,
- partialUpdateObjects,
- deleteObject,
- deleteObjects,
- deleteBy,
- clearObjects,
- browseObjects,
- getObjectPosition,
- findObject,
- exists,
- saveSynonym,
- saveSynonyms,
- getSynonym,
- searchSynonyms,
- browseSynonyms,
- deleteSynonym,
- clearSynonyms,
- replaceAllObjects,
- replaceAllSynonyms,
- searchRules,
- getRule,
- deleteRule,
- saveRule,
- saveRules,
- replaceAllRules,
- browseRules,
- clearRules,
- },
- });
- },
- initAnalytics: () => (clientOptions) => {
- return createAnalyticsClient({
- ...commonOptions,
- ...clientOptions,
- methods: {
- addABTest,
- getABTest,
- getABTests,
- stopABTest,
- deleteABTest,
- },
- });
- },
- initPersonalization,
- initRecommendation: () => (clientOptions) => {
- searchClientOptions.logger.info('The `initRecommendation` method is deprecated. Use `initPersonalization` instead.');
- return initPersonalization()(clientOptions);
- },
- },
- });
- }
- // eslint-disable-next-line functional/immutable-data
- algoliasearch.version = version;
- export default algoliasearch;
|