client-search.esm.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276
  1. import { createAuth, AuthMode, shuffle, addMethods, createWaitablePromise, createRetryablePromise, encode } from '@algolia/client-common';
  2. import { createTransporter, CallEnum, createMappedRequestOptions, serializeQueryParameters } from '@algolia/transporter';
  3. import { MethodEnum } from '@algolia/requester-common';
  4. import { createHmac } from 'crypto';
  5. function createBrowsablePromise(options) {
  6. const browse = (data) => {
  7. return options.request(data).then(response => {
  8. /**
  9. * First we send to the developer the
  10. * batch retrieved from the API.
  11. */
  12. if (options.batch !== undefined) {
  13. options.batch(response.hits);
  14. }
  15. /**
  16. * Then, we ask to the browse concrete implementation
  17. * if we should stop browsing. As example, the `browseObjects`
  18. * method will stop if the cursor is not present on the response.
  19. */
  20. if (options.shouldStop(response)) {
  21. return undefined;
  22. }
  23. /**
  24. * Finally, if the response contains a cursor, we browse to the next
  25. * batch using that same cursor. Otherwise, we just use the traditional
  26. * browsing using the page element.
  27. */
  28. if (response.cursor) {
  29. return browse({
  30. cursor: response.cursor,
  31. });
  32. }
  33. return browse({
  34. page: (data.page || 0) + 1,
  35. });
  36. });
  37. };
  38. return browse({});
  39. }
  40. const createSearchClient = options => {
  41. const appId = options.appId;
  42. const auth = createAuth(options.authMode !== undefined ? options.authMode : AuthMode.WithinHeaders, appId, options.apiKey);
  43. const transporter = createTransporter({
  44. hosts: [
  45. { url: `${appId}-dsn.algolia.net`, accept: CallEnum.Read },
  46. { url: `${appId}.algolia.net`, accept: CallEnum.Write },
  47. ].concat(shuffle([
  48. { url: `${appId}-1.algolianet.com` },
  49. { url: `${appId}-2.algolianet.com` },
  50. { url: `${appId}-3.algolianet.com` },
  51. ])),
  52. ...options,
  53. headers: {
  54. ...auth.headers(),
  55. ...{ 'content-type': 'application/x-www-form-urlencoded' },
  56. ...options.headers,
  57. },
  58. queryParameters: {
  59. ...auth.queryParameters(),
  60. ...options.queryParameters,
  61. },
  62. });
  63. const base = {
  64. transporter,
  65. appId,
  66. addAlgoliaAgent(segment, version) {
  67. transporter.userAgent.add({ segment, version });
  68. },
  69. clearCache() {
  70. return Promise.all([
  71. transporter.requestsCache.clear(),
  72. transporter.responsesCache.clear(),
  73. ]).then(() => undefined);
  74. },
  75. };
  76. return addMethods(base, options.methods);
  77. };
  78. function createMissingObjectIDError() {
  79. return {
  80. name: 'MissingObjectIDError',
  81. message: 'All objects must have an unique objectID ' +
  82. '(like a primary key) to be valid. ' +
  83. 'Algolia is also able to generate objectIDs ' +
  84. "automatically but *it's not recommended*. " +
  85. "To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option.",
  86. };
  87. }
  88. function createObjectNotFoundError() {
  89. return {
  90. name: 'ObjectNotFoundError',
  91. message: 'Object not found.',
  92. };
  93. }
  94. function createValidUntilNotFoundError() {
  95. return {
  96. name: 'ValidUntilNotFoundError',
  97. message: 'ValidUntil not found in given secured api key.',
  98. };
  99. }
  100. const addApiKey = (base) => {
  101. return (acl, requestOptions) => {
  102. const { queryParameters, ...options } = requestOptions || {};
  103. const data = {
  104. acl,
  105. ...(queryParameters !== undefined ? { queryParameters } : {}),
  106. };
  107. const wait = (response, waitRequestOptions) => {
  108. return createRetryablePromise(retry => {
  109. return getApiKey(base)(response.key, waitRequestOptions).catch((apiError) => {
  110. if (apiError.status !== 404) {
  111. throw apiError;
  112. }
  113. return retry();
  114. });
  115. });
  116. };
  117. return createWaitablePromise(base.transporter.write({
  118. method: MethodEnum.Post,
  119. path: '1/keys',
  120. data,
  121. }, options), wait);
  122. };
  123. };
  124. const assignUserID = (base) => {
  125. return (userID, clusterName, requestOptions) => {
  126. const mappedRequestOptions = createMappedRequestOptions(requestOptions);
  127. // eslint-disable-next-line functional/immutable-data
  128. mappedRequestOptions.queryParameters['X-Algolia-User-ID'] = userID;
  129. return base.transporter.write({
  130. method: MethodEnum.Post,
  131. path: '1/clusters/mapping',
  132. data: { cluster: clusterName },
  133. }, mappedRequestOptions);
  134. };
  135. };
  136. const assignUserIDs = (base) => {
  137. return (userIDs, clusterName, requestOptions) => {
  138. return base.transporter.write({
  139. method: MethodEnum.Post,
  140. path: '1/clusters/mapping/batch',
  141. data: {
  142. users: userIDs,
  143. cluster: clusterName,
  144. },
  145. }, requestOptions);
  146. };
  147. };
  148. const clearDictionaryEntries = (base) => {
  149. return (dictionary, requestOptions) => {
  150. return createWaitablePromise(base.transporter.write({
  151. method: MethodEnum.Post,
  152. path: encode('/1/dictionaries/%s/batch', dictionary),
  153. data: {
  154. clearExistingDictionaryEntries: true,
  155. requests: { action: 'addEntry', body: [] },
  156. },
  157. }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
  158. };
  159. };
  160. const copyIndex = (base) => {
  161. return (from, to, requestOptions) => {
  162. const wait = (response, waitRequestOptions) => {
  163. return initIndex(base)(from, {
  164. methods: { waitTask },
  165. }).waitTask(response.taskID, waitRequestOptions);
  166. };
  167. return createWaitablePromise(base.transporter.write({
  168. method: MethodEnum.Post,
  169. path: encode('1/indexes/%s/operation', from),
  170. data: {
  171. operation: 'copy',
  172. destination: to,
  173. },
  174. }, requestOptions), wait);
  175. };
  176. };
  177. const copyRules = (base) => {
  178. return (from, to, requestOptions) => {
  179. return copyIndex(base)(from, to, {
  180. ...requestOptions,
  181. scope: [ScopeEnum.Rules],
  182. });
  183. };
  184. };
  185. const copySettings = (base) => {
  186. return (from, to, requestOptions) => {
  187. return copyIndex(base)(from, to, {
  188. ...requestOptions,
  189. scope: [ScopeEnum.Settings],
  190. });
  191. };
  192. };
  193. const copySynonyms = (base) => {
  194. return (from, to, requestOptions) => {
  195. return copyIndex(base)(from, to, {
  196. ...requestOptions,
  197. scope: [ScopeEnum.Synonyms],
  198. });
  199. };
  200. };
  201. const customRequest = (base) => {
  202. return (request, requestOptions) => {
  203. if (request.method === MethodEnum.Get) {
  204. return base.transporter.read(request, requestOptions);
  205. }
  206. return base.transporter.write(request, requestOptions);
  207. };
  208. };
  209. const deleteApiKey = (base) => {
  210. return (apiKey, requestOptions) => {
  211. const wait = (_, waitRequestOptions) => {
  212. return createRetryablePromise(retry => {
  213. return getApiKey(base)(apiKey, waitRequestOptions)
  214. .then(retry)
  215. .catch((apiError) => {
  216. if (apiError.status !== 404) {
  217. throw apiError;
  218. }
  219. });
  220. });
  221. };
  222. return createWaitablePromise(base.transporter.write({
  223. method: MethodEnum.Delete,
  224. path: encode('1/keys/%s', apiKey),
  225. }, requestOptions), wait);
  226. };
  227. };
  228. const deleteDictionaryEntries = (base) => {
  229. return (dictionary, objectIDs, requestOptions) => {
  230. const requests = objectIDs.map(objectID => ({
  231. action: 'deleteEntry',
  232. body: { objectID },
  233. }));
  234. return createWaitablePromise(base.transporter.write({
  235. method: MethodEnum.Post,
  236. path: encode('/1/dictionaries/%s/batch', dictionary),
  237. data: { clearExistingDictionaryEntries: false, requests },
  238. }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
  239. };
  240. };
  241. const generateSecuredApiKey = () => {
  242. return (parentApiKey, restrictions) => {
  243. const queryParameters = serializeQueryParameters(restrictions);
  244. const securedKey = createHmac('sha256', parentApiKey)
  245. .update(queryParameters)
  246. .digest('hex');
  247. return Buffer.from(securedKey + queryParameters).toString('base64');
  248. };
  249. };
  250. const getApiKey = (base) => {
  251. return (apiKey, requestOptions) => {
  252. return base.transporter.read({
  253. method: MethodEnum.Get,
  254. path: encode('1/keys/%s', apiKey),
  255. }, requestOptions);
  256. };
  257. };
  258. const getAppTask = (base) => {
  259. return (taskID, requestOptions) => {
  260. return base.transporter.read({
  261. method: MethodEnum.Get,
  262. path: encode('1/task/%s', taskID.toString()),
  263. }, requestOptions);
  264. };
  265. };
  266. const getDictionarySettings = (base) => {
  267. return (requestOptions) => {
  268. return base.transporter.read({
  269. method: MethodEnum.Get,
  270. path: '/1/dictionaries/*/settings',
  271. }, requestOptions);
  272. };
  273. };
  274. const getLogs = (base) => {
  275. return (requestOptions) => {
  276. return base.transporter.read({
  277. method: MethodEnum.Get,
  278. path: '1/logs',
  279. }, requestOptions);
  280. };
  281. };
  282. const getSecuredApiKeyRemainingValidity = () => {
  283. return (securedApiKey) => {
  284. const decodedString = Buffer.from(securedApiKey, 'base64').toString('ascii');
  285. const regex = /validUntil=(\d+)/;
  286. const match = decodedString.match(regex);
  287. if (match === null) {
  288. throw createValidUntilNotFoundError();
  289. }
  290. return parseInt(match[1], 10) - Math.round(new Date().getTime() / 1000);
  291. };
  292. };
  293. const getTopUserIDs = (base) => {
  294. return (requestOptions) => {
  295. return base.transporter.read({
  296. method: MethodEnum.Get,
  297. path: '1/clusters/mapping/top',
  298. }, requestOptions);
  299. };
  300. };
  301. const getUserID = (base) => {
  302. return (userID, requestOptions) => {
  303. return base.transporter.read({
  304. method: MethodEnum.Get,
  305. path: encode('1/clusters/mapping/%s', userID),
  306. }, requestOptions);
  307. };
  308. };
  309. const hasPendingMappings = (base) => {
  310. return (requestOptions) => {
  311. const { retrieveMappings, ...options } = requestOptions || {};
  312. if (retrieveMappings === true) {
  313. // eslint-disable-next-line functional/immutable-data
  314. options.getClusters = true;
  315. }
  316. return base.transporter.read({
  317. method: MethodEnum.Get,
  318. path: '1/clusters/mapping/pending',
  319. }, options);
  320. };
  321. };
  322. const initIndex = (base) => {
  323. return (indexName, options = {}) => {
  324. const searchIndex = {
  325. transporter: base.transporter,
  326. appId: base.appId,
  327. indexName,
  328. };
  329. return addMethods(searchIndex, options.methods);
  330. };
  331. };
  332. const listApiKeys = (base) => {
  333. return (requestOptions) => {
  334. return base.transporter.read({
  335. method: MethodEnum.Get,
  336. path: '1/keys',
  337. }, requestOptions);
  338. };
  339. };
  340. const listClusters = (base) => {
  341. return (requestOptions) => {
  342. return base.transporter.read({
  343. method: MethodEnum.Get,
  344. path: '1/clusters',
  345. }, requestOptions);
  346. };
  347. };
  348. const listIndices = (base) => {
  349. return (requestOptions) => {
  350. return base.transporter.read({
  351. method: MethodEnum.Get,
  352. path: '1/indexes',
  353. }, requestOptions);
  354. };
  355. };
  356. const listUserIDs = (base) => {
  357. return (requestOptions) => {
  358. return base.transporter.read({
  359. method: MethodEnum.Get,
  360. path: '1/clusters/mapping',
  361. }, requestOptions);
  362. };
  363. };
  364. const moveIndex = (base) => {
  365. return (from, to, requestOptions) => {
  366. const wait = (response, waitRequestOptions) => {
  367. return initIndex(base)(from, {
  368. methods: { waitTask },
  369. }).waitTask(response.taskID, waitRequestOptions);
  370. };
  371. return createWaitablePromise(base.transporter.write({
  372. method: MethodEnum.Post,
  373. path: encode('1/indexes/%s/operation', from),
  374. data: {
  375. operation: 'move',
  376. destination: to,
  377. },
  378. }, requestOptions), wait);
  379. };
  380. };
  381. const multipleBatch = (base) => {
  382. return (requests, requestOptions) => {
  383. const wait = (response, waitRequestOptions) => {
  384. return Promise.all(Object.keys(response.taskID).map(indexName => {
  385. return initIndex(base)(indexName, {
  386. methods: { waitTask },
  387. }).waitTask(response.taskID[indexName], waitRequestOptions);
  388. }));
  389. };
  390. return createWaitablePromise(base.transporter.write({
  391. method: MethodEnum.Post,
  392. path: '1/indexes/*/batch',
  393. data: {
  394. requests,
  395. },
  396. }, requestOptions), wait);
  397. };
  398. };
  399. const multipleGetObjects = (base) => {
  400. return (requests, requestOptions) => {
  401. return base.transporter.read({
  402. method: MethodEnum.Post,
  403. path: '1/indexes/*/objects',
  404. data: {
  405. requests,
  406. },
  407. }, requestOptions);
  408. };
  409. };
  410. const multipleQueries = (base) => {
  411. return (queries, requestOptions) => {
  412. const requests = queries.map(query => {
  413. return {
  414. ...query,
  415. params: serializeQueryParameters(query.params || {}),
  416. };
  417. });
  418. return base.transporter.read({
  419. method: MethodEnum.Post,
  420. path: '1/indexes/*/queries',
  421. data: {
  422. requests,
  423. },
  424. cacheable: true,
  425. }, requestOptions);
  426. };
  427. };
  428. const multipleSearchForFacetValues = (base) => {
  429. return (queries, requestOptions) => {
  430. return Promise.all(queries.map(query => {
  431. const { facetName, facetQuery, ...params } = query.params;
  432. return initIndex(base)(query.indexName, {
  433. methods: { searchForFacetValues },
  434. }).searchForFacetValues(facetName, facetQuery, {
  435. ...requestOptions,
  436. ...params,
  437. });
  438. }));
  439. };
  440. };
  441. const removeUserID = (base) => {
  442. return (userID, requestOptions) => {
  443. const mappedRequestOptions = createMappedRequestOptions(requestOptions);
  444. // eslint-disable-next-line functional/immutable-data
  445. mappedRequestOptions.queryParameters['X-Algolia-User-ID'] = userID;
  446. return base.transporter.write({
  447. method: MethodEnum.Delete,
  448. path: '1/clusters/mapping',
  449. }, mappedRequestOptions);
  450. };
  451. };
  452. const replaceDictionaryEntries = (base) => {
  453. return (dictionary, entries, requestOptions) => {
  454. const requests = entries.map(entry => ({
  455. action: 'addEntry',
  456. body: entry,
  457. }));
  458. return createWaitablePromise(base.transporter.write({
  459. method: MethodEnum.Post,
  460. path: encode('/1/dictionaries/%s/batch', dictionary),
  461. data: { clearExistingDictionaryEntries: true, requests },
  462. }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
  463. };
  464. };
  465. const restoreApiKey = (base) => {
  466. return (apiKey, requestOptions) => {
  467. const wait = (_, waitRequestOptions) => {
  468. return createRetryablePromise(retry => {
  469. return getApiKey(base)(apiKey, waitRequestOptions).catch((apiError) => {
  470. if (apiError.status !== 404) {
  471. throw apiError;
  472. }
  473. return retry();
  474. });
  475. });
  476. };
  477. return createWaitablePromise(base.transporter.write({
  478. method: MethodEnum.Post,
  479. path: encode('1/keys/%s/restore', apiKey),
  480. }, requestOptions), wait);
  481. };
  482. };
  483. const saveDictionaryEntries = (base) => {
  484. return (dictionary, entries, requestOptions) => {
  485. const requests = entries.map(entry => ({
  486. action: 'addEntry',
  487. body: entry,
  488. }));
  489. return createWaitablePromise(base.transporter.write({
  490. method: MethodEnum.Post,
  491. path: encode('/1/dictionaries/%s/batch', dictionary),
  492. data: { clearExistingDictionaryEntries: false, requests },
  493. }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
  494. };
  495. };
  496. const searchDictionaryEntries = (base) => {
  497. return (dictionary, query, requestOptions) => {
  498. return base.transporter.read({
  499. method: MethodEnum.Post,
  500. path: encode('/1/dictionaries/%s/search', dictionary),
  501. data: {
  502. query,
  503. },
  504. cacheable: true,
  505. }, requestOptions);
  506. };
  507. };
  508. const searchUserIDs = (base) => {
  509. return (query, requestOptions) => {
  510. return base.transporter.read({
  511. method: MethodEnum.Post,
  512. path: '1/clusters/mapping/search',
  513. data: {
  514. query,
  515. },
  516. }, requestOptions);
  517. };
  518. };
  519. const setDictionarySettings = (base) => {
  520. return (settings, requestOptions) => {
  521. return createWaitablePromise(base.transporter.write({
  522. method: MethodEnum.Put,
  523. path: '/1/dictionaries/*/settings',
  524. data: settings,
  525. }, requestOptions), (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions));
  526. };
  527. };
  528. const updateApiKey = (base) => {
  529. return (apiKey, requestOptions) => {
  530. const updatedFields = Object.assign({}, requestOptions);
  531. const { queryParameters, ...options } = requestOptions || {};
  532. const data = queryParameters ? { queryParameters } : {};
  533. const apiKeyFields = [
  534. 'acl',
  535. 'indexes',
  536. 'referers',
  537. 'restrictSources',
  538. 'queryParameters',
  539. 'description',
  540. 'maxQueriesPerIPPerHour',
  541. 'maxHitsPerQuery',
  542. ];
  543. const hasChanged = (getApiKeyResponse) => {
  544. return Object.keys(updatedFields)
  545. .filter((updatedField) => apiKeyFields.indexOf(updatedField) !== -1)
  546. .every(updatedField => {
  547. return getApiKeyResponse[updatedField] === updatedFields[updatedField];
  548. });
  549. };
  550. const wait = (_, waitRequestOptions) => createRetryablePromise(retry => {
  551. return getApiKey(base)(apiKey, waitRequestOptions).then(getApiKeyResponse => {
  552. return hasChanged(getApiKeyResponse) ? Promise.resolve() : retry();
  553. });
  554. });
  555. return createWaitablePromise(base.transporter.write({
  556. method: MethodEnum.Put,
  557. path: encode('1/keys/%s', apiKey),
  558. data,
  559. }, options), wait);
  560. };
  561. };
  562. const waitAppTask = (base) => {
  563. return (taskID, requestOptions) => {
  564. return createRetryablePromise(retry => {
  565. return getAppTask(base)(taskID, requestOptions).then(response => {
  566. return response.status !== 'published' ? retry() : undefined;
  567. });
  568. });
  569. };
  570. };
  571. const batch = (base) => {
  572. return (requests, requestOptions) => {
  573. const wait = (response, waitRequestOptions) => {
  574. return waitTask(base)(response.taskID, waitRequestOptions);
  575. };
  576. return createWaitablePromise(base.transporter.write({
  577. method: MethodEnum.Post,
  578. path: encode('1/indexes/%s/batch', base.indexName),
  579. data: {
  580. requests,
  581. },
  582. }, requestOptions), wait);
  583. };
  584. };
  585. const browseObjects = (base) => {
  586. return (requestOptions) => {
  587. return createBrowsablePromise({
  588. shouldStop: response => response.cursor === undefined,
  589. ...requestOptions,
  590. request: (data) => base.transporter.read({
  591. method: MethodEnum.Post,
  592. path: encode('1/indexes/%s/browse', base.indexName),
  593. data,
  594. }, requestOptions),
  595. });
  596. };
  597. };
  598. const browseRules = (base) => {
  599. return (requestOptions) => {
  600. const options = {
  601. hitsPerPage: 1000,
  602. ...requestOptions,
  603. };
  604. return createBrowsablePromise({
  605. shouldStop: response => response.hits.length < options.hitsPerPage,
  606. ...options,
  607. request(data) {
  608. return searchRules(base)('', { ...options, ...data }).then((response) => {
  609. return {
  610. ...response,
  611. hits: response.hits.map(rule => {
  612. // eslint-disable-next-line functional/immutable-data,no-param-reassign
  613. delete rule._highlightResult;
  614. return rule;
  615. }),
  616. };
  617. });
  618. },
  619. });
  620. };
  621. };
  622. const browseSynonyms = (base) => {
  623. return (requestOptions) => {
  624. const options = {
  625. hitsPerPage: 1000,
  626. ...requestOptions,
  627. };
  628. return createBrowsablePromise({
  629. shouldStop: response => response.hits.length < options.hitsPerPage,
  630. ...options,
  631. request(data) {
  632. return searchSynonyms(base)('', { ...options, ...data }).then((response) => {
  633. return {
  634. ...response,
  635. hits: response.hits.map(synonym => {
  636. // eslint-disable-next-line functional/immutable-data,no-param-reassign
  637. delete synonym._highlightResult;
  638. return synonym;
  639. }),
  640. };
  641. });
  642. },
  643. });
  644. };
  645. };
  646. const chunkedBatch = (base) => {
  647. return (bodies, action, requestOptions) => {
  648. const { batchSize, ...options } = requestOptions || {};
  649. const response = {
  650. taskIDs: [],
  651. objectIDs: [],
  652. };
  653. const forEachBatch = (lastIndex = 0) => {
  654. // eslint-disable-next-line functional/prefer-readonly-type
  655. const bodiesChunk = [];
  656. // eslint-disable-next-line functional/no-let
  657. let index;
  658. /* eslint-disable-next-line functional/no-loop-statement */
  659. for (index = lastIndex; index < bodies.length; index++) {
  660. // eslint-disable-next-line functional/immutable-data
  661. bodiesChunk.push(bodies[index]);
  662. if (bodiesChunk.length === (batchSize || 1000)) {
  663. break;
  664. }
  665. }
  666. if (bodiesChunk.length === 0) {
  667. return Promise.resolve(response);
  668. }
  669. return batch(base)(bodiesChunk.map(body => {
  670. return {
  671. action,
  672. body,
  673. };
  674. }), options).then(res => {
  675. response.objectIDs = response.objectIDs.concat(res.objectIDs); // eslint-disable-line functional/immutable-data
  676. response.taskIDs.push(res.taskID); // eslint-disable-line functional/immutable-data
  677. index++;
  678. return forEachBatch(index);
  679. });
  680. };
  681. return createWaitablePromise(forEachBatch(), (chunkedBatchResponse, waitRequestOptions) => {
  682. return Promise.all(chunkedBatchResponse.taskIDs.map(taskID => {
  683. return waitTask(base)(taskID, waitRequestOptions);
  684. }));
  685. });
  686. };
  687. };
  688. const clearObjects = (base) => {
  689. return (requestOptions) => {
  690. return createWaitablePromise(base.transporter.write({
  691. method: MethodEnum.Post,
  692. path: encode('1/indexes/%s/clear', base.indexName),
  693. }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  694. };
  695. };
  696. const clearRules = (base) => {
  697. return (requestOptions) => {
  698. const { forwardToReplicas, ...options } = requestOptions || {};
  699. const mappedRequestOptions = createMappedRequestOptions(options);
  700. if (forwardToReplicas) {
  701. mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
  702. }
  703. return createWaitablePromise(base.transporter.write({
  704. method: MethodEnum.Post,
  705. path: encode('1/indexes/%s/rules/clear', base.indexName),
  706. }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  707. };
  708. };
  709. const clearSynonyms = (base) => {
  710. return (requestOptions) => {
  711. const { forwardToReplicas, ...options } = requestOptions || {};
  712. const mappedRequestOptions = createMappedRequestOptions(options);
  713. if (forwardToReplicas) {
  714. mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
  715. }
  716. return createWaitablePromise(base.transporter.write({
  717. method: MethodEnum.Post,
  718. path: encode('1/indexes/%s/synonyms/clear', base.indexName),
  719. }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  720. };
  721. };
  722. const deleteBy = (base) => {
  723. return (filters, requestOptions) => {
  724. return createWaitablePromise(base.transporter.write({
  725. method: MethodEnum.Post,
  726. path: encode('1/indexes/%s/deleteByQuery', base.indexName),
  727. data: filters,
  728. }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  729. };
  730. };
  731. const deleteIndex = (base) => {
  732. return (requestOptions) => {
  733. return createWaitablePromise(base.transporter.write({
  734. method: MethodEnum.Delete,
  735. path: encode('1/indexes/%s', base.indexName),
  736. }, requestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  737. };
  738. };
  739. const deleteObject = (base) => {
  740. return (objectID, requestOptions) => {
  741. return createWaitablePromise(deleteObjects(base)([objectID], requestOptions).then(response => {
  742. return { taskID: response.taskIDs[0] };
  743. }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  744. };
  745. };
  746. const deleteObjects = (base) => {
  747. return (objectIDs, requestOptions) => {
  748. const objects = objectIDs.map(objectID => {
  749. return { objectID };
  750. });
  751. return chunkedBatch(base)(objects, BatchActionEnum.DeleteObject, requestOptions);
  752. };
  753. };
  754. const deleteRule = (base) => {
  755. return (objectID, requestOptions) => {
  756. const { forwardToReplicas, ...options } = requestOptions || {};
  757. const mappedRequestOptions = createMappedRequestOptions(options);
  758. if (forwardToReplicas) {
  759. mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
  760. }
  761. return createWaitablePromise(base.transporter.write({
  762. method: MethodEnum.Delete,
  763. path: encode('1/indexes/%s/rules/%s', base.indexName, objectID),
  764. }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  765. };
  766. };
  767. const deleteSynonym = (base) => {
  768. return (objectID, requestOptions) => {
  769. const { forwardToReplicas, ...options } = requestOptions || {};
  770. const mappedRequestOptions = createMappedRequestOptions(options);
  771. if (forwardToReplicas) {
  772. mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
  773. }
  774. return createWaitablePromise(base.transporter.write({
  775. method: MethodEnum.Delete,
  776. path: encode('1/indexes/%s/synonyms/%s', base.indexName, objectID),
  777. }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  778. };
  779. };
  780. const exists = (base) => {
  781. return (requestOptions) => {
  782. return getSettings(base)(requestOptions)
  783. .then(() => true)
  784. .catch(error => {
  785. if (error.status !== 404) {
  786. throw error;
  787. }
  788. return false;
  789. });
  790. };
  791. };
  792. const findAnswers = (base) => {
  793. return (query, queryLanguages, requestOptions) => {
  794. return base.transporter.read({
  795. method: MethodEnum.Post,
  796. path: encode('1/answers/%s/prediction', base.indexName),
  797. data: {
  798. query,
  799. queryLanguages,
  800. },
  801. cacheable: true,
  802. }, requestOptions);
  803. };
  804. };
  805. const findObject = (base) => {
  806. return (callback, requestOptions) => {
  807. const { query, paginate, ...options } = requestOptions || {};
  808. // eslint-disable-next-line functional/no-let
  809. let page = 0;
  810. const forEachPage = () => {
  811. return search(base)(query || '', { ...options, page }).then(result => {
  812. // eslint-disable-next-line functional/no-loop-statement
  813. for (const [position, hit] of Object.entries(result.hits)) {
  814. // eslint-disable-next-line promise/no-callback-in-promise
  815. if (callback(hit)) {
  816. return {
  817. object: hit,
  818. position: parseInt(position, 10),
  819. page,
  820. };
  821. }
  822. }
  823. page++;
  824. // paginate if option was set and has next page
  825. if (paginate === false || page >= result.nbPages) {
  826. throw createObjectNotFoundError();
  827. }
  828. return forEachPage();
  829. });
  830. };
  831. return forEachPage();
  832. };
  833. };
  834. const getObject = (base) => {
  835. return (objectID, requestOptions) => {
  836. return base.transporter.read({
  837. method: MethodEnum.Get,
  838. path: encode('1/indexes/%s/%s', base.indexName, objectID),
  839. }, requestOptions);
  840. };
  841. };
  842. const getObjectPosition = () => {
  843. return (searchResponse, objectID) => {
  844. // eslint-disable-next-line functional/no-loop-statement
  845. for (const [position, hit] of Object.entries(searchResponse.hits)) {
  846. if (hit.objectID === objectID) {
  847. return parseInt(position, 10);
  848. }
  849. }
  850. return -1;
  851. };
  852. };
  853. const getObjects = (base) => {
  854. return (objectIDs, requestOptions) => {
  855. const { attributesToRetrieve, ...options } = requestOptions || {};
  856. const requests = objectIDs.map(objectID => {
  857. return {
  858. indexName: base.indexName,
  859. objectID,
  860. ...(attributesToRetrieve ? { attributesToRetrieve } : {}),
  861. };
  862. });
  863. return base.transporter.read({
  864. method: MethodEnum.Post,
  865. path: '1/indexes/*/objects',
  866. data: {
  867. requests,
  868. },
  869. }, options);
  870. };
  871. };
  872. const getRule = (base) => {
  873. return (objectID, requestOptions) => {
  874. return base.transporter.read({
  875. method: MethodEnum.Get,
  876. path: encode('1/indexes/%s/rules/%s', base.indexName, objectID),
  877. }, requestOptions);
  878. };
  879. };
  880. const getSettings = (base) => {
  881. return (requestOptions) => {
  882. return base.transporter.read({
  883. method: MethodEnum.Get,
  884. path: encode('1/indexes/%s/settings', base.indexName),
  885. data: {
  886. getVersion: 2,
  887. },
  888. }, requestOptions);
  889. };
  890. };
  891. const getSynonym = (base) => {
  892. return (objectID, requestOptions) => {
  893. return base.transporter.read({
  894. method: MethodEnum.Get,
  895. path: encode(`1/indexes/%s/synonyms/%s`, base.indexName, objectID),
  896. }, requestOptions);
  897. };
  898. };
  899. const getTask = (base) => {
  900. return (taskID, requestOptions) => {
  901. return base.transporter.read({
  902. method: MethodEnum.Get,
  903. path: encode('1/indexes/%s/task/%s', base.indexName, taskID.toString()),
  904. }, requestOptions);
  905. };
  906. };
  907. const partialUpdateObject = (base) => {
  908. return (object, requestOptions) => {
  909. return createWaitablePromise(partialUpdateObjects(base)([object], requestOptions).then(response => {
  910. return {
  911. objectID: response.objectIDs[0],
  912. taskID: response.taskIDs[0],
  913. };
  914. }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  915. };
  916. };
  917. const partialUpdateObjects = (base) => {
  918. return (objects, requestOptions) => {
  919. const { createIfNotExists, ...options } = requestOptions || {};
  920. const action = createIfNotExists
  921. ? BatchActionEnum.PartialUpdateObject
  922. : BatchActionEnum.PartialUpdateObjectNoCreate;
  923. return chunkedBatch(base)(objects, action, options);
  924. };
  925. };
  926. const replaceAllObjects = (base) => {
  927. return (objects, requestOptions) => {
  928. const { safe, autoGenerateObjectIDIfNotExist, batchSize, ...options } = requestOptions || {};
  929. const operation = (from, to, type, operationRequestOptions) => {
  930. return createWaitablePromise(base.transporter.write({
  931. method: MethodEnum.Post,
  932. path: encode('1/indexes/%s/operation', from),
  933. data: {
  934. operation: type,
  935. destination: to,
  936. },
  937. }, operationRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  938. };
  939. const randomSuffix = Math.random()
  940. .toString(36)
  941. .substring(7);
  942. const temporaryIndexName = `${base.indexName}_tmp_${randomSuffix}`;
  943. const saveObjectsInTemporary = saveObjects({
  944. appId: base.appId,
  945. transporter: base.transporter,
  946. indexName: temporaryIndexName,
  947. });
  948. // @ts-ignore
  949. // eslint-disable-next-line prefer-const, functional/no-let, functional/prefer-readonly-type
  950. let responses = [];
  951. const copyWaitablePromise = operation(base.indexName, temporaryIndexName, 'copy', {
  952. ...options,
  953. scope: ['settings', 'synonyms', 'rules'],
  954. });
  955. // eslint-disable-next-line functional/immutable-data
  956. responses.push(copyWaitablePromise);
  957. const result = (safe
  958. ? copyWaitablePromise.wait(options)
  959. : copyWaitablePromise)
  960. .then(() => {
  961. const saveObjectsWaitablePromise = saveObjectsInTemporary(objects, {
  962. ...options,
  963. autoGenerateObjectIDIfNotExist,
  964. batchSize,
  965. });
  966. // eslint-disable-next-line functional/immutable-data
  967. responses.push(saveObjectsWaitablePromise);
  968. return safe ? saveObjectsWaitablePromise.wait(options) : saveObjectsWaitablePromise;
  969. })
  970. .then(() => {
  971. const moveWaitablePromise = operation(temporaryIndexName, base.indexName, 'move', options);
  972. // eslint-disable-next-line functional/immutable-data
  973. responses.push(moveWaitablePromise);
  974. return safe ? moveWaitablePromise.wait(options) : moveWaitablePromise;
  975. })
  976. .then(() => Promise.all(responses))
  977. .then(([copyResponse, saveObjectsResponse, moveResponse]) => {
  978. return {
  979. objectIDs: saveObjectsResponse.objectIDs,
  980. taskIDs: [copyResponse.taskID, ...saveObjectsResponse.taskIDs, moveResponse.taskID],
  981. };
  982. });
  983. return createWaitablePromise(result, (_, waitRequestOptions) => {
  984. return Promise.all(responses.map(response => response.wait(waitRequestOptions)));
  985. });
  986. };
  987. };
  988. const replaceAllRules = (base) => {
  989. return (rules, requestOptions) => {
  990. return saveRules(base)(rules, {
  991. ...requestOptions,
  992. clearExistingRules: true,
  993. });
  994. };
  995. };
  996. const replaceAllSynonyms = (base) => {
  997. return (synonyms, requestOptions) => {
  998. return saveSynonyms(base)(synonyms, {
  999. ...requestOptions,
  1000. clearExistingSynonyms: true,
  1001. });
  1002. };
  1003. };
  1004. const saveObject = (base) => {
  1005. return (object, requestOptions) => {
  1006. return createWaitablePromise(saveObjects(base)([object], requestOptions).then(response => {
  1007. return {
  1008. objectID: response.objectIDs[0],
  1009. taskID: response.taskIDs[0],
  1010. };
  1011. }), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  1012. };
  1013. };
  1014. const saveObjects = (base) => {
  1015. return (objects, requestOptions) => {
  1016. const { autoGenerateObjectIDIfNotExist, ...options } = requestOptions || {};
  1017. const action = autoGenerateObjectIDIfNotExist
  1018. ? BatchActionEnum.AddObject
  1019. : BatchActionEnum.UpdateObject;
  1020. if (action === BatchActionEnum.UpdateObject) {
  1021. // eslint-disable-next-line functional/no-loop-statement
  1022. for (const object of objects) {
  1023. if (object.objectID === undefined) {
  1024. return createWaitablePromise(Promise.reject(createMissingObjectIDError()));
  1025. }
  1026. }
  1027. }
  1028. return chunkedBatch(base)(objects, action, options);
  1029. };
  1030. };
  1031. const saveRule = (base) => {
  1032. return (rule, requestOptions) => {
  1033. return saveRules(base)([rule], requestOptions);
  1034. };
  1035. };
  1036. const saveRules = (base) => {
  1037. return (rules, requestOptions) => {
  1038. const { forwardToReplicas, clearExistingRules, ...options } = requestOptions || {};
  1039. const mappedRequestOptions = createMappedRequestOptions(options);
  1040. if (forwardToReplicas) {
  1041. mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
  1042. }
  1043. if (clearExistingRules) {
  1044. mappedRequestOptions.queryParameters.clearExistingRules = 1; // eslint-disable-line functional/immutable-data
  1045. }
  1046. return createWaitablePromise(base.transporter.write({
  1047. method: MethodEnum.Post,
  1048. path: encode('1/indexes/%s/rules/batch', base.indexName),
  1049. data: rules,
  1050. }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  1051. };
  1052. };
  1053. const saveSynonym = (base) => {
  1054. return (synonym, requestOptions) => {
  1055. return saveSynonyms(base)([synonym], requestOptions);
  1056. };
  1057. };
  1058. const saveSynonyms = (base) => {
  1059. return (synonyms, requestOptions) => {
  1060. const { forwardToReplicas, clearExistingSynonyms, replaceExistingSynonyms, ...options } = requestOptions || {};
  1061. const mappedRequestOptions = createMappedRequestOptions(options);
  1062. if (forwardToReplicas) {
  1063. mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
  1064. }
  1065. if (replaceExistingSynonyms || clearExistingSynonyms) {
  1066. mappedRequestOptions.queryParameters.replaceExistingSynonyms = 1; // eslint-disable-line functional/immutable-data
  1067. }
  1068. return createWaitablePromise(base.transporter.write({
  1069. method: MethodEnum.Post,
  1070. path: encode('1/indexes/%s/synonyms/batch', base.indexName),
  1071. data: synonyms,
  1072. }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  1073. };
  1074. };
  1075. const search = (base) => {
  1076. return (query, requestOptions) => {
  1077. return base.transporter.read({
  1078. method: MethodEnum.Post,
  1079. path: encode('1/indexes/%s/query', base.indexName),
  1080. data: {
  1081. query,
  1082. },
  1083. cacheable: true,
  1084. }, requestOptions);
  1085. };
  1086. };
  1087. const searchForFacetValues = (base) => {
  1088. return (facetName, facetQuery, requestOptions) => {
  1089. return base.transporter.read({
  1090. method: MethodEnum.Post,
  1091. path: encode('1/indexes/%s/facets/%s/query', base.indexName, facetName),
  1092. data: {
  1093. facetQuery,
  1094. },
  1095. cacheable: true,
  1096. }, requestOptions);
  1097. };
  1098. };
  1099. const searchRules = (base) => {
  1100. return (query, requestOptions) => {
  1101. return base.transporter.read({
  1102. method: MethodEnum.Post,
  1103. path: encode('1/indexes/%s/rules/search', base.indexName),
  1104. data: {
  1105. query,
  1106. },
  1107. }, requestOptions);
  1108. };
  1109. };
  1110. const searchSynonyms = (base) => {
  1111. return (query, requestOptions) => {
  1112. return base.transporter.read({
  1113. method: MethodEnum.Post,
  1114. path: encode('1/indexes/%s/synonyms/search', base.indexName),
  1115. data: {
  1116. query,
  1117. },
  1118. }, requestOptions);
  1119. };
  1120. };
  1121. const setSettings = (base) => {
  1122. return (settings, requestOptions) => {
  1123. const { forwardToReplicas, ...options } = requestOptions || {};
  1124. const mappedRequestOptions = createMappedRequestOptions(options);
  1125. if (forwardToReplicas) {
  1126. mappedRequestOptions.queryParameters.forwardToReplicas = 1; // eslint-disable-line functional/immutable-data
  1127. }
  1128. return createWaitablePromise(base.transporter.write({
  1129. method: MethodEnum.Put,
  1130. path: encode('1/indexes/%s/settings', base.indexName),
  1131. data: settings,
  1132. }, mappedRequestOptions), (response, waitRequestOptions) => waitTask(base)(response.taskID, waitRequestOptions));
  1133. };
  1134. };
  1135. const waitTask = (base) => {
  1136. return (taskID, requestOptions) => {
  1137. return createRetryablePromise(retry => {
  1138. return getTask(base)(taskID, requestOptions).then(response => {
  1139. return response.status !== 'published' ? retry() : undefined;
  1140. });
  1141. });
  1142. };
  1143. };
  1144. const ApiKeyACLEnum = {
  1145. AddObject: 'addObject',
  1146. Analytics: 'analytics',
  1147. Browser: 'browse',
  1148. DeleteIndex: 'deleteIndex',
  1149. DeleteObject: 'deleteObject',
  1150. EditSettings: 'editSettings',
  1151. ListIndexes: 'listIndexes',
  1152. Logs: 'logs',
  1153. Personalization: 'personalization',
  1154. Recommendation: 'recommendation',
  1155. Search: 'search',
  1156. SeeUnretrievableAttributes: 'seeUnretrievableAttributes',
  1157. Settings: 'settings',
  1158. Usage: 'usage',
  1159. };
  1160. const BatchActionEnum = {
  1161. AddObject: 'addObject',
  1162. UpdateObject: 'updateObject',
  1163. PartialUpdateObject: 'partialUpdateObject',
  1164. PartialUpdateObjectNoCreate: 'partialUpdateObjectNoCreate',
  1165. DeleteObject: 'deleteObject',
  1166. DeleteIndex: 'delete',
  1167. ClearIndex: 'clear',
  1168. };
  1169. const ScopeEnum = {
  1170. Settings: 'settings',
  1171. Synonyms: 'synonyms',
  1172. Rules: 'rules',
  1173. };
  1174. const StrategyEnum = {
  1175. None: 'none',
  1176. StopIfEnoughMatches: 'stopIfEnoughMatches',
  1177. };
  1178. const SynonymEnum = {
  1179. Synonym: 'synonym',
  1180. OneWaySynonym: 'oneWaySynonym',
  1181. AltCorrection1: 'altCorrection1',
  1182. AltCorrection2: 'altCorrection2',
  1183. Placeholder: 'placeholder',
  1184. };
  1185. export { ApiKeyACLEnum, BatchActionEnum, ScopeEnum, StrategyEnum, SynonymEnum, addApiKey, assignUserID, assignUserIDs, batch, browseObjects, browseRules, browseSynonyms, chunkedBatch, clearDictionaryEntries, clearObjects, clearRules, clearSynonyms, copyIndex, copyRules, copySettings, copySynonyms, createBrowsablePromise, createMissingObjectIDError, createObjectNotFoundError, createSearchClient, createValidUntilNotFoundError, customRequest, deleteApiKey, deleteBy, deleteDictionaryEntries, deleteIndex, deleteObject, deleteObjects, deleteRule, deleteSynonym, exists, findAnswers, findObject, generateSecuredApiKey, getApiKey, getAppTask, getDictionarySettings, getLogs, getObject, getObjectPosition, getObjects, getRule, getSecuredApiKeyRemainingValidity, getSettings, getSynonym, getTask, getTopUserIDs, getUserID, hasPendingMappings, initIndex, listApiKeys, listClusters, listIndices, listUserIDs, moveIndex, multipleBatch, multipleGetObjects, multipleQueries, multipleSearchForFacetValues, partialUpdateObject, partialUpdateObjects, removeUserID, replaceAllObjects, replaceAllRules, replaceAllSynonyms, replaceDictionaryEntries, restoreApiKey, saveDictionaryEntries, saveObject, saveObjects, saveRule, saveRules, saveSynonym, saveSynonyms, search, searchDictionaryEntries, searchForFacetValues, searchRules, searchSynonyms, searchUserIDs, setDictionarySettings, setSettings, updateApiKey, waitAppTask, waitTask };