From aed6554f795300fc8b180ef7cb4cf54913c5a1a6 Mon Sep 17 00:00:00 2001 From: Chloe Liban Date: Tue, 13 Apr 2021 10:21:53 +0200 Subject: [PATCH] feat(dictionaries): adds methods and tests (#1253) * feat(dictionaries): adds methods and tests * rename dictionary task to app task * semicolon * add missing types * group tests * remove `dictionary` tests from `browser-lite` env * fix methods * split and fix tests * Rename `SaveDictionaryEntriesOptions` type to `DictionaryEntriesOptions` * Rename `SaveDictionaryEntriesResponse` type to `DictionaryEntriesResponse` * Remove duplicate type: `SetDictionarySettingsResponse` * Assert object is present * Remove unneeded spread Co-authored-by: Haroen Viaene Co-authored-by: shortcuts --- jest.config.js | 2 + package.json | 2 +- packages/algoliasearch/src/builds/browser.ts | 61 ++++++++ packages/algoliasearch/src/builds/node.ts | 61 ++++++++ .../__tests__/integration/dictionary.test.ts | 144 ++++++++++++++++++ .../methods/client/clearDictionaryEntries.ts | 33 ++++ .../methods/client/deleteDictionaryEntries.ts | 36 +++++ .../src/methods/client/getAppTask.ts | 20 +++ .../methods/client/getDictionarySettings.ts | 16 ++ .../client-search/src/methods/client/index.ts | 9 ++ .../client/replaceDictionaryEntries.ts | 37 +++++ .../methods/client/saveDictionaryEntries.ts | 37 +++++ .../methods/client/searchDictionaryEntries.ts | 25 +++ .../methods/client/setDictionarySettings.ts | 25 +++ .../src/methods/client/waitAppTask.ts | 15 ++ .../src/types/DictionaryEntriesOptions.ts | 8 + .../src/types/DictionaryEntriesResponse.ts | 11 ++ .../src/types/DictionaryEntry.ts | 16 ++ .../client-search/src/types/DictionaryName.ts | 1 + .../src/types/DictionarySettings.ts | 11 ++ .../types/GetDictionarySettingsResponse.ts | 11 ++ .../src/types/RequireAtLeastOne.ts | 4 + .../types/SearchDictionaryEntriesResponse.ts | 26 ++++ packages/client-search/src/types/index.ts | 7 + 24 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 packages/client-search/src/__tests__/integration/dictionary.test.ts create mode 100644 packages/client-search/src/methods/client/clearDictionaryEntries.ts create mode 100644 packages/client-search/src/methods/client/deleteDictionaryEntries.ts create mode 100644 packages/client-search/src/methods/client/getAppTask.ts create mode 100644 packages/client-search/src/methods/client/getDictionarySettings.ts create mode 100644 packages/client-search/src/methods/client/replaceDictionaryEntries.ts create mode 100644 packages/client-search/src/methods/client/saveDictionaryEntries.ts create mode 100644 packages/client-search/src/methods/client/searchDictionaryEntries.ts create mode 100644 packages/client-search/src/methods/client/setDictionarySettings.ts create mode 100644 packages/client-search/src/methods/client/waitAppTask.ts create mode 100644 packages/client-search/src/types/DictionaryEntriesOptions.ts create mode 100644 packages/client-search/src/types/DictionaryEntriesResponse.ts create mode 100644 packages/client-search/src/types/DictionaryEntry.ts create mode 100644 packages/client-search/src/types/DictionaryName.ts create mode 100644 packages/client-search/src/types/DictionarySettings.ts create mode 100644 packages/client-search/src/types/GetDictionarySettingsResponse.ts create mode 100644 packages/client-search/src/types/RequireAtLeastOne.ts create mode 100644 packages/client-search/src/types/SearchDictionaryEntriesResponse.ts diff --git a/jest.config.js b/jest.config.js index 883687c91..f0135e341 100644 --- a/jest.config.js +++ b/jest.config.js @@ -48,6 +48,7 @@ module.exports = { 'packages/client-search/src/__tests__/integration/secured-api-keys.test.ts', 'packages/client-search/src/__tests__/integration/settings.test.ts', 'packages/client-search/src/__tests__/integration/synonyms.test.ts', + 'packages/client-search/src/__tests__/integration/dictionary.test.ts', ], globals: { environment: 'browser-lite', @@ -65,6 +66,7 @@ module.exports = { testPathIgnorePatterns: [ 'packages/requester-node-http/*', 'packages/client-search/src/__tests__/integration/secured-api-keys.test.ts', + 'packages/client-search/src/__tests__/integration/dictionary.test.ts', ], globals: { environment: 'browser', diff --git a/package.json b/package.json index df5b74d10..824ee6901 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "bundlesize": [ { "path": "packages/algoliasearch/dist/algoliasearch.umd.js", - "maxSize": "7.65KB" + "maxSize": "7.85KB" }, { "path": "packages/algoliasearch/dist/algoliasearch-lite.umd.js", diff --git a/packages/algoliasearch/src/builds/browser.ts b/packages/algoliasearch/src/builds/browser.ts index f8976e59d..cb7bb484b 100644 --- a/packages/algoliasearch/src/builds/browser.ts +++ b/packages/algoliasearch/src/builds/browser.ts @@ -45,6 +45,7 @@ import { browseSynonyms, ChunkedBatchResponse, ChunkOptions, + clearDictionaryEntries, clearObjects, clearRules, ClearRulesOptions, @@ -60,6 +61,7 @@ import { DeleteApiKeyResponse, deleteBy, DeleteByFiltersOptions, + deleteDictionaryEntries, deleteIndex, deleteObject, deleteObjects, @@ -67,6 +69,11 @@ import { deleteRule, deleteSynonym, DeleteSynonymOptions, + DictionaryEntriesOptions, + DictionaryEntriesResponse, + DictionaryEntry, + DictionaryName, + DictionarySettings, exists, findAnswers, FindAnswersOptions, @@ -76,6 +83,9 @@ import { FindObjectResponse, getApiKey, GetApiKeyResponse, + getAppTask, + getDictionarySettings, + GetDictionarySettingsResponse, getLogs, GetLogsResponse, getObject, @@ -127,9 +137,11 @@ import { ReplaceAllObjectsOptions, replaceAllRules, replaceAllSynonyms, + replaceDictionaryEntries, restoreApiKey, RestoreApiKeyResponse, Rule, + saveDictionaryEntries, saveObject, SaveObjectResponse, saveObjects, @@ -146,6 +158,8 @@ import { SaveSynonymsResponse, search, SearchClient as BaseSearchClient, + searchDictionaryEntries, + SearchDictionaryEntriesResponse, searchForFacetValues, SearchForFacetValuesQueryParams, SearchForFacetValuesResponse, @@ -160,14 +174,17 @@ import { searchUserIDs, SearchUserIDsOptions, SearchUserIDsResponse, + setDictionarySettings, setSettings, SetSettingsResponse, Settings, Synonym, + TaskStatusResponse, updateApiKey, UpdateApiKeyOptions, UpdateApiKeyResponse, UserIDResponse, + waitAppTask, waitTask, } from '@algolia/client-search'; import { LogLevelEnum } from '@algolia/logger-common'; @@ -235,6 +252,15 @@ export default function algoliasearch( getTopUserIDs, removeUserID, hasPendingMappings, + clearDictionaryEntries, + deleteDictionaryEntries, + getDictionarySettings, + getAppTask, + replaceDictionaryEntries, + saveDictionaryEntries, + searchDictionaryEntries, + setDictionarySettings, + waitAppTask, initIndex: base => (indexName: string): SearchIndex => { return initIndex(base)(indexName, { methods: { @@ -601,6 +627,41 @@ export type SearchClient = BaseSearchClient & { readonly hasPendingMappings: ( requestOptions?: HasPendingMappingsOptions & RequestOptions ) => Readonly>; + readonly clearDictionaryEntries: ( + dictionary: DictionaryName, + requestOptions?: RequestOptions & DictionaryEntriesOptions + ) => Readonly>; + readonly deleteDictionaryEntries: ( + dictionary: DictionaryName, + objectIDs: readonly string[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ) => Readonly>; + readonly replaceDictionaryEntries: ( + dictionary: DictionaryName, + entries: readonly DictionaryEntry[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ) => Readonly>; + readonly saveDictionaryEntries: ( + dictionary: DictionaryName, + entries: readonly DictionaryEntry[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ) => Readonly>; + readonly searchDictionaryEntries: ( + dictionary: DictionaryName, + query: string, + requestOptions?: RequestOptions + ) => Readonly>; + readonly getDictionarySettings: ( + requestOptions?: RequestOptions + ) => Readonly>; + readonly setDictionarySettings: ( + settings: DictionarySettings, + requestOptions?: RequestOptions + ) => Readonly>; + readonly getAppTask: ( + taskID: number, + requestOptions?: RequestOptions + ) => Readonly>; readonly initAnalytics: (options?: InitAnalyticsOptions) => AnalyticsClient; readonly initRecommendation: (options?: InitRecommendationOptions) => RecommendationClient; }; diff --git a/packages/algoliasearch/src/builds/node.ts b/packages/algoliasearch/src/builds/node.ts index b1030fd6d..439605f04 100644 --- a/packages/algoliasearch/src/builds/node.ts +++ b/packages/algoliasearch/src/builds/node.ts @@ -44,6 +44,7 @@ import { browseSynonyms, ChunkedBatchResponse, ChunkOptions, + clearDictionaryEntries, clearObjects, clearRules, ClearRulesOptions, @@ -59,6 +60,7 @@ import { DeleteApiKeyResponse, deleteBy, DeleteByFiltersOptions, + deleteDictionaryEntries, deleteIndex, deleteObject, deleteObjects, @@ -66,6 +68,11 @@ import { deleteRule, deleteSynonym, DeleteSynonymOptions, + DictionaryEntriesOptions, + DictionaryEntriesResponse, + DictionaryEntry, + DictionaryName, + DictionarySettings, exists, findAnswers, FindAnswersOptions, @@ -76,6 +83,9 @@ import { generateSecuredApiKey, getApiKey, GetApiKeyResponse, + getAppTask, + getDictionarySettings, + GetDictionarySettingsResponse, getLogs, GetLogsResponse, getObject, @@ -128,9 +138,11 @@ import { ReplaceAllObjectsOptions, replaceAllRules, replaceAllSynonyms, + replaceDictionaryEntries, restoreApiKey, RestoreApiKeyResponse, Rule, + saveDictionaryEntries, saveObject, SaveObjectResponse, saveObjects, @@ -147,6 +159,8 @@ import { SaveSynonymsResponse, search, SearchClient as BaseSearchClient, + searchDictionaryEntries, + SearchDictionaryEntriesResponse, searchForFacetValues, SearchForFacetValuesQueryParams, SearchForFacetValuesResponse, @@ -162,14 +176,17 @@ import { SearchUserIDsOptions, SearchUserIDsResponse, SecuredApiKeyRestrictions, + setDictionarySettings, setSettings, SetSettingsResponse, Settings, Synonym, + TaskStatusResponse, updateApiKey, UpdateApiKeyOptions, UpdateApiKeyResponse, UserIDResponse, + waitAppTask, waitTask, } from '@algolia/client-search'; import { createNullLogger } from '@algolia/logger-common'; @@ -238,6 +255,15 @@ export default function algoliasearch( generateSecuredApiKey, getSecuredApiKeyRemainingValidity, destroy, + clearDictionaryEntries, + deleteDictionaryEntries, + getDictionarySettings, + getAppTask, + replaceDictionaryEntries, + saveDictionaryEntries, + searchDictionaryEntries, + setDictionarySettings, + waitAppTask, initIndex: base => (indexName: string): SearchIndex => { return initIndex(base)(indexName, { methods: { @@ -609,6 +635,41 @@ export type SearchClient = BaseSearchClient & { restrictions: SecuredApiKeyRestrictions ) => string; readonly getSecuredApiKeyRemainingValidity: (securedApiKey: string) => number; + readonly clearDictionaryEntries: ( + dictionary: DictionaryName, + requestOptions?: RequestOptions & DictionaryEntriesOptions + ) => Readonly>; + readonly deleteDictionaryEntries: ( + dictionary: DictionaryName, + objectIDs: readonly string[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ) => Readonly>; + readonly replaceDictionaryEntries: ( + dictionary: DictionaryName, + entries: readonly DictionaryEntry[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ) => Readonly>; + readonly saveDictionaryEntries: ( + dictionary: DictionaryName, + entries: readonly DictionaryEntry[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ) => Readonly>; + readonly searchDictionaryEntries: ( + dictionary: DictionaryName, + query: string, + requestOptions?: RequestOptions + ) => Readonly>; + readonly getDictionarySettings: ( + requestOptions?: RequestOptions + ) => Readonly>; + readonly setDictionarySettings: ( + settings: DictionarySettings, + requestOptions?: RequestOptions + ) => Readonly>; + readonly getAppTask: ( + taskID: number, + requestOptions?: RequestOptions + ) => Readonly>; readonly initAnalytics: (options?: InitAnalyticsOptions) => AnalyticsClient; readonly initRecommendation: (options?: InitRecommendationOptions) => RecommendationClient; } & Destroyable; diff --git a/packages/client-search/src/__tests__/integration/dictionary.test.ts b/packages/client-search/src/__tests__/integration/dictionary.test.ts new file mode 100644 index 000000000..61aabf1fb --- /dev/null +++ b/packages/client-search/src/__tests__/integration/dictionary.test.ts @@ -0,0 +1,144 @@ +/* eslint-disable no-param-reassign */ +import { TestSuite } from '../../../../client-common/src/__tests__/TestSuite'; + +const testSuite = new TestSuite('dictionary'); + +describe(testSuite.testName, () => { + const client = testSuite.makeSearchClient('ALGOLIA_APPLICATION_ID_2', 'ALGOLIA_ADMIN_KEY_2'); + + test('stopwords', async () => { + const stopwordEntry = { + objectID: Math.floor(Math.random() * 10000).toString(), + language: 'en', + word: 'down', + }; + + // clean past entries + await client + .deleteDictionaryEntries( + 'stopwords', + (await client.searchDictionaryEntries('stopwords', stopwordEntry.objectID)).hits.map( + hit => hit.objectID + ) + ) + .wait(); + + const nbSearchEntries = ( + await client.searchDictionaryEntries('stopwords', stopwordEntry.objectID) + ).nbHits; + + await client.saveDictionaryEntries('stopwords', [stopwordEntry]).wait(); + + const stopwords = await client.searchDictionaryEntries('stopwords', stopwordEntry.objectID); + + expect(stopwords.nbHits).toEqual(nbSearchEntries + 1); + expect(stopwords.hits).toEqual( + expect.arrayContaining([expect.objectContaining(stopwordEntry)]) + ); + + await client.deleteDictionaryEntries('stopwords', [stopwordEntry.objectID]).wait(); + expect( + (await client.searchDictionaryEntries('stopwords', stopwordEntry.objectID)).nbHits + ).toEqual(0); + + const oldDictionaryState = await client.searchDictionaryEntries('stopwords', ''); + const oldDictionaryEntries = oldDictionaryState.hits.map(hit => { + // @ts-ignore + delete hit.type; + + return hit; + }); + + await client.saveDictionaryEntries('stopwords', [stopwordEntry]).wait(); + expect( + (await client.searchDictionaryEntries('stopwords', stopwordEntry.objectID)).nbHits + ).toEqual(1); + + await client.replaceDictionaryEntries('stopwords', oldDictionaryEntries).wait(); + expect( + (await client.searchDictionaryEntries('stopwords', stopwordEntry.objectID)).nbHits + ).toEqual(0); + + const stopwordsSettings = { + disableStandardEntries: { + stopwords: { + en: true, + fr: true, + }, + }, + }; + + await client.setDictionarySettings(stopwordsSettings); + expect(await client.getDictionarySettings()).toEqual(stopwordsSettings); + }); + + test('plurals', async () => { + const pluralEntry = { + objectID: Math.floor(Math.random() * 10000).toString(), + language: 'fr', + words: ['cheval', 'chevaux'], + }; + + // clean past entries + await client + .deleteDictionaryEntries( + 'plurals', + (await client.searchDictionaryEntries('plurals', pluralEntry.objectID)).hits.map( + hit => hit.objectID + ) + ) + .wait(); + + const nbSearchEntries = (await client.searchDictionaryEntries('plurals', pluralEntry.objectID)) + .nbHits; + + await client.saveDictionaryEntries('plurals', [pluralEntry]).wait(); + + const plurals = await client.searchDictionaryEntries('plurals', pluralEntry.objectID); + + expect(plurals.nbHits).toEqual(nbSearchEntries + 1); + expect(plurals.hits).toEqual(expect.arrayContaining([expect.objectContaining(pluralEntry)])); + + await client.deleteDictionaryEntries('plurals', [pluralEntry.objectID]).wait(); + expect((await client.searchDictionaryEntries('plurals', pluralEntry.objectID)).nbHits).toEqual( + 0 + ); + }); + + test('compounds', async () => { + const compoundEntry = { + objectID: Math.floor(Math.random() * 10000).toString(), + language: 'de', + word: 'kopfschmerztablette', + decomposition: ['kopf', 'schmerz', 'tablette'], + }; + + // clean past entries + await client + .deleteDictionaryEntries( + 'compounds', + (await client.searchDictionaryEntries('compounds', compoundEntry.objectID)).hits.map( + hit => hit.objectID + ) + ) + .wait(); + + const nbSearchEntries = ( + await client.searchDictionaryEntries('compounds', compoundEntry.objectID) + ).nbHits; + + await client.saveDictionaryEntries('compounds', [compoundEntry]).wait(); + + const compounds = await client.searchDictionaryEntries('compounds', compoundEntry.objectID); + + expect(compounds.nbHits).toEqual(nbSearchEntries + 1); + expect(compounds.hits).toEqual( + expect.arrayContaining([expect.objectContaining(compoundEntry)]) + ); + + await client.deleteDictionaryEntries('compounds', [compoundEntry.objectID]).wait(); + expect( + (await client.searchDictionaryEntries('compounds', compoundEntry.objectID)).nbHits + ).toEqual(0); + }); +}); diff --git a/packages/client-search/src/methods/client/clearDictionaryEntries.ts b/packages/client-search/src/methods/client/clearDictionaryEntries.ts new file mode 100644 index 000000000..e89e574de --- /dev/null +++ b/packages/client-search/src/methods/client/clearDictionaryEntries.ts @@ -0,0 +1,33 @@ +import { createWaitablePromise, encode, WaitablePromise } from '@algolia/client-common'; +import { MethodEnum } from '@algolia/requester-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { + DictionaryEntriesOptions, + DictionaryEntriesResponse, + DictionaryName, + SearchClient, +} from '../..'; +import { waitAppTask } from '.'; + +export const clearDictionaryEntries = (base: SearchClient) => { + return ( + dictionary: DictionaryName, + requestOptions?: RequestOptions & DictionaryEntriesOptions + ): Readonly> => { + 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) + ); + }; +}; diff --git a/packages/client-search/src/methods/client/deleteDictionaryEntries.ts b/packages/client-search/src/methods/client/deleteDictionaryEntries.ts new file mode 100644 index 000000000..c29e4dbf7 --- /dev/null +++ b/packages/client-search/src/methods/client/deleteDictionaryEntries.ts @@ -0,0 +1,36 @@ +import { createWaitablePromise, encode, WaitablePromise } from '@algolia/client-common'; +import { MethodEnum } from '@algolia/requester-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { + DictionaryEntriesOptions, + DictionaryEntriesResponse, + DictionaryName, + SearchClient, +} from '../..'; +import { waitAppTask } from '.'; + +export const deleteDictionaryEntries = (base: SearchClient) => { + return ( + dictionary: DictionaryName, + objectIDs: readonly string[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ): Readonly> => { + 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) + ); + }; +}; diff --git a/packages/client-search/src/methods/client/getAppTask.ts b/packages/client-search/src/methods/client/getAppTask.ts new file mode 100644 index 000000000..7805babf2 --- /dev/null +++ b/packages/client-search/src/methods/client/getAppTask.ts @@ -0,0 +1,20 @@ +import { encode } from '@algolia/client-common'; +import { MethodEnum } from '@algolia/requester-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { SearchClient, TaskStatusResponse } from '../..'; + +export const getAppTask = (base: SearchClient) => { + return ( + taskID: number, + requestOptions?: RequestOptions + ): Readonly> => { + return base.transporter.read( + { + method: MethodEnum.Get, + path: encode('1/task/%s', taskID.toString()), + }, + requestOptions + ); + }; +}; diff --git a/packages/client-search/src/methods/client/getDictionarySettings.ts b/packages/client-search/src/methods/client/getDictionarySettings.ts new file mode 100644 index 000000000..cb4758347 --- /dev/null +++ b/packages/client-search/src/methods/client/getDictionarySettings.ts @@ -0,0 +1,16 @@ +import { MethodEnum } from '@algolia/requester-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { GetDictionarySettingsResponse, SearchClient } from '../..'; + +export const getDictionarySettings = (base: SearchClient) => { + return (requestOptions?: RequestOptions): Readonly> => { + return base.transporter.read( + { + method: MethodEnum.Get, + path: '/1/dictionaries/*/settings', + }, + requestOptions + ); + }; +}; diff --git a/packages/client-search/src/methods/client/index.ts b/packages/client-search/src/methods/client/index.ts index c0ed301c0..9cc3f64f2 100644 --- a/packages/client-search/src/methods/client/index.ts +++ b/packages/client-search/src/methods/client/index.ts @@ -5,16 +5,20 @@ export * from './addApiKey'; export * from './assignUserID'; export * from './assignUserIDs'; +export * from './clearDictionaryEntries'; export * from './copyIndex'; export * from './copyRules'; export * from './copySettings'; export * from './copySynonyms'; export * from './deleteApiKey'; +export * from './deleteDictionaryEntries'; export * from './generateSecuredApiKey'; export * from './getApiKey'; +export * from './getDictionarySettings'; export * from './getLogs'; export * from './getSecuredApiKeyRemainingValidity'; export * from './getTopUserIDs'; +export * from './getAppTask'; export * from './getUserID'; export * from './hasPendingMappings'; export * from './initIndex'; @@ -28,6 +32,11 @@ export * from './multipleGetObjects'; export * from './multipleQueries'; export * from './multipleSearchForFacetValues'; export * from './removeUserID'; +export * from './replaceDictionaryEntries'; export * from './restoreApiKey'; +export * from './saveDictionaryEntries'; +export * from './searchDictionaryEntries'; export * from './searchUserIDs'; +export * from './setDictionarySettings'; export * from './updateApiKey'; +export * from './waitAppTask'; diff --git a/packages/client-search/src/methods/client/replaceDictionaryEntries.ts b/packages/client-search/src/methods/client/replaceDictionaryEntries.ts new file mode 100644 index 000000000..4e9c24734 --- /dev/null +++ b/packages/client-search/src/methods/client/replaceDictionaryEntries.ts @@ -0,0 +1,37 @@ +import { createWaitablePromise, encode, WaitablePromise } from '@algolia/client-common'; +import { MethodEnum } from '@algolia/requester-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { + DictionaryEntriesOptions, + DictionaryEntriesResponse, + DictionaryEntry, + DictionaryName, + SearchClient, +} from '../..'; +import { waitAppTask } from '.'; + +export const replaceDictionaryEntries = (base: SearchClient) => { + return ( + dictionary: DictionaryName, + entries: readonly DictionaryEntry[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ): Readonly> => { + 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) + ); + }; +}; diff --git a/packages/client-search/src/methods/client/saveDictionaryEntries.ts b/packages/client-search/src/methods/client/saveDictionaryEntries.ts new file mode 100644 index 000000000..5b6a6b5c4 --- /dev/null +++ b/packages/client-search/src/methods/client/saveDictionaryEntries.ts @@ -0,0 +1,37 @@ +import { createWaitablePromise, encode, WaitablePromise } from '@algolia/client-common'; +import { MethodEnum } from '@algolia/requester-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { + DictionaryEntriesOptions, + DictionaryEntriesResponse, + DictionaryEntry, + DictionaryName, + SearchClient, +} from '../..'; +import { waitAppTask } from '.'; + +export const saveDictionaryEntries = (base: SearchClient) => { + return ( + dictionary: DictionaryName, + entries: readonly DictionaryEntry[], + requestOptions?: RequestOptions & DictionaryEntriesOptions + ): Readonly> => { + 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) + ); + }; +}; diff --git a/packages/client-search/src/methods/client/searchDictionaryEntries.ts b/packages/client-search/src/methods/client/searchDictionaryEntries.ts new file mode 100644 index 000000000..dc45abd90 --- /dev/null +++ b/packages/client-search/src/methods/client/searchDictionaryEntries.ts @@ -0,0 +1,25 @@ +import { encode } from '@algolia/client-common'; +import { MethodEnum } from '@algolia/requester-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { DictionaryName, SearchClient, SearchDictionaryEntriesResponse } from '../..'; + +export const searchDictionaryEntries = (base: SearchClient) => { + return ( + dictionary: DictionaryName, + query: string, + requestOptions?: RequestOptions + ): Readonly> => { + return base.transporter.read( + { + method: MethodEnum.Post, + path: encode('/1/dictionaries/%s/search', dictionary), + data: { + query, + }, + cacheable: true, + }, + requestOptions + ); + }; +}; diff --git a/packages/client-search/src/methods/client/setDictionarySettings.ts b/packages/client-search/src/methods/client/setDictionarySettings.ts new file mode 100644 index 000000000..5ceebc6d4 --- /dev/null +++ b/packages/client-search/src/methods/client/setDictionarySettings.ts @@ -0,0 +1,25 @@ +import { createWaitablePromise, WaitablePromise } from '@algolia/client-common'; +import { MethodEnum } from '@algolia/requester-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { DictionaryEntriesResponse, DictionarySettings, SearchClient } from '../..'; +import { waitAppTask } from '.'; + +export const setDictionarySettings = (base: SearchClient) => { + return ( + settings: DictionarySettings, + requestOptions?: RequestOptions + ): Readonly> => { + return createWaitablePromise( + base.transporter.write( + { + method: MethodEnum.Put, + path: '/1/dictionaries/*/settings', + data: settings, + }, + requestOptions + ), + (response, waitRequestOptions) => waitAppTask(base)(response.taskID, waitRequestOptions) + ); + }; +}; diff --git a/packages/client-search/src/methods/client/waitAppTask.ts b/packages/client-search/src/methods/client/waitAppTask.ts new file mode 100644 index 000000000..ae25111ae --- /dev/null +++ b/packages/client-search/src/methods/client/waitAppTask.ts @@ -0,0 +1,15 @@ +import { createRetryablePromise } from '@algolia/client-common'; +import { RequestOptions } from '@algolia/transporter'; + +import { SearchClient } from '../..'; +import { getAppTask } from '.'; + +export const waitAppTask = (base: SearchClient) => { + return (taskID: number, requestOptions?: RequestOptions): Readonly> => { + return createRetryablePromise(retry => { + return getAppTask(base)(taskID, requestOptions).then(response => { + return response.status !== 'published' ? retry() : undefined; + }); + }); + }; +}; diff --git a/packages/client-search/src/types/DictionaryEntriesOptions.ts b/packages/client-search/src/types/DictionaryEntriesOptions.ts new file mode 100644 index 000000000..5769d1915 --- /dev/null +++ b/packages/client-search/src/types/DictionaryEntriesOptions.ts @@ -0,0 +1,8 @@ +import { DictionaryEntry } from './DictionaryEntry'; + +export type DictionaryEntriesOptions = { + /** + * Array of dictionary entries + */ + readonly dictionaryEntries: readonly DictionaryEntry[]; +}; diff --git a/packages/client-search/src/types/DictionaryEntriesResponse.ts b/packages/client-search/src/types/DictionaryEntriesResponse.ts new file mode 100644 index 000000000..1ae88d50c --- /dev/null +++ b/packages/client-search/src/types/DictionaryEntriesResponse.ts @@ -0,0 +1,11 @@ +export type DictionaryEntriesResponse = { + /** + * When the given rules got saved. + */ + updatedAt: number; + + /** + * The operation task id. May be used to perform a wait task. + */ + taskID: number; +}; diff --git a/packages/client-search/src/types/DictionaryEntry.ts b/packages/client-search/src/types/DictionaryEntry.ts new file mode 100644 index 000000000..f3c061ddd --- /dev/null +++ b/packages/client-search/src/types/DictionaryEntry.ts @@ -0,0 +1,16 @@ +export type DictionaryEntry = { + /** + * Unique identifier for the rule (format: [A-Za-z0-9_-]+). + */ + readonly objectID: string; + + readonly language: string; + + readonly word?: string; + + readonly words?: readonly string[]; + + readonly decomposition?: readonly string[]; + + readonly state?: 'enabled' | 'disabled'; +}; diff --git a/packages/client-search/src/types/DictionaryName.ts b/packages/client-search/src/types/DictionaryName.ts new file mode 100644 index 000000000..46333d441 --- /dev/null +++ b/packages/client-search/src/types/DictionaryName.ts @@ -0,0 +1 @@ +export type DictionaryName = 'plurals' | 'stopwords' | 'compounds'; diff --git a/packages/client-search/src/types/DictionarySettings.ts b/packages/client-search/src/types/DictionarySettings.ts new file mode 100644 index 000000000..2a6a4c48d --- /dev/null +++ b/packages/client-search/src/types/DictionarySettings.ts @@ -0,0 +1,11 @@ +import { DictionaryName } from './DictionaryName'; +import { RequireAtLeastOne } from './RequireAtLeastOne'; + +export type DictionarySettings = { + /** + * Disable the builtin Algolia entries for a type of dictionary per language. + */ + readonly disableStandardEntries: RequireAtLeastOne< + Record> + >; +}; diff --git a/packages/client-search/src/types/GetDictionarySettingsResponse.ts b/packages/client-search/src/types/GetDictionarySettingsResponse.ts new file mode 100644 index 000000000..88ddad87d --- /dev/null +++ b/packages/client-search/src/types/GetDictionarySettingsResponse.ts @@ -0,0 +1,11 @@ +import { DictionaryName } from './DictionaryName'; +import { RequireAtLeastOne } from './RequireAtLeastOne'; + +export type GetDictionarySettingsResponse = { + /** + * Disable the builtin Algolia entries for a type of dictionary per language. + */ + readonly disableStandardEntries: RequireAtLeastOne< + Record> + >; +}; diff --git a/packages/client-search/src/types/RequireAtLeastOne.ts b/packages/client-search/src/types/RequireAtLeastOne.ts new file mode 100644 index 000000000..9b2cb5406 --- /dev/null +++ b/packages/client-search/src/types/RequireAtLeastOne.ts @@ -0,0 +1,4 @@ +export type RequireAtLeastOne = { + [TKey in keyof TType]-?: Required> & + Partial>>; +}[keyof TType]; diff --git a/packages/client-search/src/types/SearchDictionaryEntriesResponse.ts b/packages/client-search/src/types/SearchDictionaryEntriesResponse.ts new file mode 100644 index 000000000..1d054e0af --- /dev/null +++ b/packages/client-search/src/types/SearchDictionaryEntriesResponse.ts @@ -0,0 +1,26 @@ +import { DictionaryEntry } from './DictionaryEntry'; + +export type SearchDictionaryEntriesResponse = { + /** + * The dictionary entries returned by the search. + */ + hits: DictionaryEntry[]; + + /** + * Index of the current page (zero-based). + */ + page: number; + + /** + * Number of dictionary entries matched by the query. + */ + nbHits: number; + + /** + * Number of pages returned. + * + * Calculation is based on the total number of hits (nbHits) divided by the + * number of hits per page (hitsPerPage), rounded up to the nearest integer. + */ + nbPages: number; +}; diff --git a/packages/client-search/src/types/index.ts b/packages/client-search/src/types/index.ts index 523dfd08e..02eb82f6c 100644 --- a/packages/client-search/src/types/index.ts +++ b/packages/client-search/src/types/index.ts @@ -24,6 +24,11 @@ export * from './DeleteApiKeyResponse'; export * from './DeleteByFiltersOptions'; export * from './DeleteResponse'; export * from './DeleteSynonymOptions'; +export * from './DictionaryEntriesOptions'; +export * from './DictionaryEntriesResponse'; +export * from './DictionaryEntry'; +export * from './DictionaryName'; +export * from './DictionarySettings'; export * from './FacetHit'; export * from './FindAnswersOptions'; export * from './FindAnswersResponse'; @@ -35,6 +40,7 @@ export * from './GetLogsResponse'; export * from './GetObjectOptions'; export * from './GetObjectsOptions'; export * from './GetObjectsResponse'; +export * from './GetDictionarySettingsResponse'; export * from './GetTopUserIDsResponse'; export * from './HasPendingMappingsOptions'; export * from './HasPendingMappingsResponse'; @@ -71,6 +77,7 @@ export * from './SaveSynonymsOptions'; export * from './SaveSynonymsResponse'; export * from './SearchClient'; export * from './SearchClientOptions'; +export * from './SearchDictionaryEntriesResponse'; export * from './SearchForFacetValuesQueryParams'; export * from './SearchForFacetValuesResponse'; export * from './SearchIndex';