From 3705a90e56af7c4a6a10da999c1e760f67756b23 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Mon, 27 Sep 2021 21:35:45 +0200 Subject: [PATCH 01/17] Add cache system --- .../facets-distribution-adapter.tests.ts | 24 +-- src/adapter/facets-distribution-adapter.ts | 10 +- src/cache/filters.ts | 29 ++-- src/cache/index.ts | 1 + src/cache/search-response.ts | 23 +++ src/client/__tests__/cache.tests.ts | 9 ++ src/client/index.ts | 137 +----------------- src/client/instant-meilisearch-client.ts | 112 ++++++++++++++ src/client/search-resolver.ts | 60 ++++++++ src/client/search.ts | 15 ++ src/types/types.ts | 15 +- src/utils/string.ts | 14 +- 12 files changed, 280 insertions(+), 169 deletions(-) create mode 100644 src/cache/search-response.ts create mode 100644 src/client/__tests__/cache.tests.ts create mode 100644 src/client/instant-meilisearch-client.ts create mode 100644 src/client/search-resolver.ts create mode 100644 src/client/search.ts diff --git a/src/adapter/__tests__/facets-distribution-adapter.tests.ts b/src/adapter/__tests__/facets-distribution-adapter.tests.ts index b0ff3b88..341407ea 100644 --- a/src/adapter/__tests__/facets-distribution-adapter.tests.ts +++ b/src/adapter/__tests__/facets-distribution-adapter.tests.ts @@ -1,7 +1,7 @@ -import { facetsDistributionAdapter } from '../' +import { adaptFacetsDistribution } from '../' test('One field in cache present in distribution', () => { - const returnedDistribution = facetsDistributionAdapter( + const returnedDistribution = adaptFacetsDistribution( { genre: ['comedy'] }, { genre: { comedy: 1 } } ) @@ -9,7 +9,7 @@ test('One field in cache present in distribution', () => { }) test('One field in cache not present in distribution', () => { - const returnedDistribution = facetsDistributionAdapter( + const returnedDistribution = adaptFacetsDistribution( { genre: ['comedy'] }, {} ) @@ -17,7 +17,7 @@ test('One field in cache not present in distribution', () => { }) test('two field in cache only one present in distribution', () => { - const returnedDistribution = facetsDistributionAdapter( + const returnedDistribution = adaptFacetsDistribution( { genre: ['comedy'], title: ['hamlet'] }, { genre: { comedy: 12 } } ) @@ -28,7 +28,7 @@ test('two field in cache only one present in distribution', () => { }) test('two field in cache w/ different facet name none present in distribution', () => { - const returnedDistribution = facetsDistributionAdapter( + const returnedDistribution = adaptFacetsDistribution( { genre: ['comedy'], title: ['hamlet'] }, {} ) @@ -39,7 +39,7 @@ test('two field in cache w/ different facet name none present in distribution', }) test('two field in cache w/ different facet name both present in distribution', () => { - const returnedDistribution = facetsDistributionAdapter( + const returnedDistribution = adaptFacetsDistribution( { genre: ['comedy'], title: ['hamlet'] }, { genre: { comedy: 12 }, title: { hamlet: 1 } } ) @@ -50,7 +50,7 @@ test('two field in cache w/ different facet name both present in distribution', }) test('Three field in cache w/ different facet name two present in distribution', () => { - const returnedDistribution = facetsDistributionAdapter( + const returnedDistribution = adaptFacetsDistribution( { genre: ['comedy', 'horror'], title: ['hamlet'] }, { genre: { comedy: 12 }, title: { hamlet: 1 } } ) @@ -61,14 +61,14 @@ test('Three field in cache w/ different facet name two present in distribution', }) test('Cache is undefined and facets distribution is not', () => { - const returnedDistribution = facetsDistributionAdapter(undefined, { + const returnedDistribution = adaptFacetsDistribution(undefined, { genre: { comedy: 12 }, }) expect(returnedDistribution).toMatchObject({ genre: { comedy: 12 } }) }) test('Cache is empty object and facets distribution is not', () => { - const returnedDistribution = facetsDistributionAdapter( + const returnedDistribution = adaptFacetsDistribution( {}, { genre: { comedy: 12 } } ) @@ -76,16 +76,16 @@ test('Cache is empty object and facets distribution is not', () => { }) test('Cache is empty object and facets distribution empty object', () => { - const returnedDistribution = facetsDistributionAdapter({}, {}) + const returnedDistribution = adaptFacetsDistribution({}, {}) expect(returnedDistribution).toMatchObject({}) }) test('Cache is undefined and facets distribution empty object', () => { - const returnedDistribution = facetsDistributionAdapter(undefined, {}) + const returnedDistribution = adaptFacetsDistribution(undefined, {}) expect(returnedDistribution).toMatchObject({}) }) test('Cache is undefined and facets distribution is undefined', () => { - const returnedDistribution = facetsDistributionAdapter(undefined, undefined) + const returnedDistribution = adaptFacetsDistribution(undefined, undefined) expect(returnedDistribution).toMatchObject({}) }) diff --git a/src/adapter/facets-distribution-adapter.ts b/src/adapter/facets-distribution-adapter.ts index 51f9e2a4..c3a3880e 100644 --- a/src/adapter/facets-distribution-adapter.ts +++ b/src/adapter/facets-distribution-adapter.ts @@ -1,4 +1,4 @@ -import { FacetsDistribution, Cache } from '../types' +import { FacetsDistribution, FilterCache } from '../types' /** * Adapt MeiliSearch facetsDistribution to instantsearch.js facetsDistribution @@ -6,12 +6,12 @@ import { FacetsDistribution, Cache } from '../types' * * To be aware of which field are checked a cache is provided that was made prior of the search request. * - * @param {Cache} cache? + * @param {FilterCache} cache? * @param {FacetsDistribution} distribution? - * @returns FacetsDistribution + * @returns {FacetsDistribution} */ -export function facetsDistributionAdapter( - cache?: Cache, +export function adaptFacetsDistribution( + cache?: FilterCache, distribution?: FacetsDistribution ): FacetsDistribution { distribution = distribution || {} diff --git a/src/cache/filters.ts b/src/cache/filters.ts index 27d33529..db7a9e15 100644 --- a/src/cache/filters.ts +++ b/src/cache/filters.ts @@ -1,4 +1,4 @@ -import { Filter, Cache, ParsedFilter } from '../types' +import { Filter, FilterCache, ParsedFilter } from '../types' import { removeUndefined } from '../utils' /** @@ -15,7 +15,7 @@ const adaptFilterSyntax = (filter: string) => { /** * @param {Filter} filters? - * @returns Array + * @returns {Array} */ function extractFilters(filters?: Filter): Array { if (typeof filters === 'string') { @@ -35,18 +35,21 @@ function extractFilters(filters?: Filter): Array { /** * @param {Filter} filters? - * @returns Cache + * @returns {FilterCache} */ -export function cacheFilters(filters?: Filter): Cache { +export function cacheFilters(filters?: Filter): FilterCache { const extractedFilters = extractFilters(filters) const cleanFilters = removeUndefined(extractedFilters) - return cleanFilters.reduce((cache, parsedFilter: ParsedFilter) => { - const { filterName, value } = parsedFilter - const prevFields = cache[filterName] || [] - cache = { - ...cache, - [filterName]: [...prevFields, value], - } - return cache - }, {} as Cache) + return cleanFilters.reduce( + (cache, parsedFilter: ParsedFilter) => { + const { filterName, value } = parsedFilter + const prevFields = cache[filterName] || [] + cache = { + ...cache, + [filterName]: [...prevFields, value], + } + return cache + }, + {} as FilterCache + ) } diff --git a/src/cache/index.ts b/src/cache/index.ts index 91e01d4d..42c39ed8 100644 --- a/src/cache/index.ts +++ b/src/cache/index.ts @@ -1 +1,2 @@ export * from './filters' +export * from './search-response' diff --git a/src/cache/search-response.ts b/src/cache/search-response.ts new file mode 100644 index 00000000..454ff468 --- /dev/null +++ b/src/cache/search-response.ts @@ -0,0 +1,23 @@ +import { ResponseCacher, MeiliSearchResponse } from '../types' +import { stringifyArray } from '../utils' +/** + * @param {Record = {} +): ResponseCacher { + const searchCache = cache + return { + getCachedValue: function (key: string) { + if (searchCache[key]) return JSON.parse(searchCache[key]) + return undefined + }, + createKey: function (components: any[]) { + return stringifyArray(components) + }, + populate: function (searchResponse: MeiliSearchResponse, key: string) { + searchCache[key] = JSON.stringify(searchResponse) + }, + } +} diff --git a/src/client/__tests__/cache.tests.ts b/src/client/__tests__/cache.tests.ts new file mode 100644 index 00000000..ba6a70c3 --- /dev/null +++ b/src/client/__tests__/cache.tests.ts @@ -0,0 +1,9 @@ +// import { SearchResolver } from '..' + +// describe('Search Resolver test', () => { +// test('Cached value', async () => { + +// }) +// }) + +// TODO diff --git a/src/client/index.ts b/src/client/index.ts index 2015b92d..0c6d0741 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,134 +1,3 @@ -import { MeiliSearch } from 'meilisearch' -import { - InstantMeiliSearchOptions, - InstantMeiliSearchInstance, - AlgoliaSearchResponse, - AlgoliaMultipleQueriesQuery, - InstantSearchParams, - SearchContext, -} from '../types' -import { - adaptSearchRequest, - adaptSearchResponse, - facetsDistributionAdapter, -} from '../adapter' -import { cacheFilters } from '../cache' - -/** - * Create search context. - * - * @param {string} indexName - * @param {InstantSearchParams} params - * @param {InstantMeiliSearchOptions={}} meiliSearchOptions - * @param {MeiliSearch} MeiliSearchClient - * @returns SearchContext - */ -function createContext( - indexName: string, - params: InstantSearchParams, - meiliSearchOptions: InstantMeiliSearchOptions = {}, - MeiliSearchClient: MeiliSearch -): SearchContext { - const { - paginationTotalHits, - primaryKey, - placeholderSearch, - } = meiliSearchOptions - - const page = params?.page - const hitsPerPage = params?.hitsPerPage - - const query = params?.query - // Split index name and possible sorting rules - const [indexUid, ...sortByArray] = indexName.split(':') - - const context = { - client: MeiliSearchClient, - indexUid: indexUid, - paginationTotalHits: paginationTotalHits || 200, - primaryKey: primaryKey || undefined, - placeholderSearch: placeholderSearch !== false, // true by default - hitsPerPage: hitsPerPage === undefined ? 20 : hitsPerPage, // 20 is the MeiliSearch's default limit value. `hitsPerPage` can be changed with `InsantSearch.configure`. - page: page || 0, // default page is 0 if none is provided - sort: sortByArray.join(':') || '', - query, - } - return context -} - -/** - * Instanciate SearchClient required by instantsearch.js. - * - * @param {string} hostUrl - * @param {string} apiKey - * @param {InstantMeiliSearchOptions={}} meiliSearchOptions - * @returns InstantMeiliSearchInstance - */ -export function instantMeiliSearch( - hostUrl: string, - apiKey: string, - meiliSearchOptions: InstantMeiliSearchOptions = {} -): InstantMeiliSearchInstance { - return { - MeiliSearchClient: new MeiliSearch({ host: hostUrl, apiKey: apiKey }), - search: async function >( - instantSearchRequests: readonly AlgoliaMultipleQueriesQuery[] - // options?: RequestOptions & MultipleQueriesOptions - When is this used ? - ): Promise<{ results: Array> }> { - try { - const searchRequest = instantSearchRequests[0] - const { params: instantSearchParams } = searchRequest - - const context = createContext( - searchRequest.indexName, - instantSearchParams, - meiliSearchOptions, - this.MeiliSearchClient - ) - - // Adapt search request to MeiliSearch compliant search request - const adaptedSearchRequest = adaptSearchRequest( - instantSearchParams, - context.paginationTotalHits, - context.placeholderSearch, - context.sort, - context.query - ) - - // Cache filters - const cachedFacet = cacheFilters(adaptedSearchRequest?.filter) - - // Executes the search with MeiliSearch - const searchResponse = await context.client - .index(context.indexUid) - .search(context.query, adaptedSearchRequest) - - // Add the checked facet attributes in facetsDistribution and give them a value of 0 - searchResponse.facetsDistribution = facetsDistributionAdapter( - cachedFacet, - searchResponse.facetsDistribution - ) - - // Adapt the MeiliSearch responsne to a compliant instantsearch.js response - const adaptedSearchResponse = adaptSearchResponse( - context.indexUid, - searchResponse, - instantSearchParams, - context - ) - return adaptedSearchResponse - } catch (e: any) { - console.error(e) - throw new Error(e) - } - }, - searchForFacetValues: async function (_) { - return await new Promise((resolve, reject) => { - reject( - new Error('SearchForFacetValues is not compatible with MeiliSearch') - ) - resolve([]) // added here to avoid compilation error - }) - }, - } -} +export * from './instant-meilisearch-client' +export * from './search' +export * from './search-resolver' diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts new file mode 100644 index 00000000..d9ac7c6a --- /dev/null +++ b/src/client/instant-meilisearch-client.ts @@ -0,0 +1,112 @@ +import { MeiliSearch } from 'meilisearch' +import { + InstantMeiliSearchOptions, + InstantMeiliSearchInstance, + AlgoliaSearchResponse, + AlgoliaMultipleQueriesQuery, + InstantSearchParams, + SearchContext, +} from '../types' +import { adaptSearchResponse } from '../adapter' +import { SearchResolver } from '.' +import { ResponseCache } from '../cache' + +/** + * Create search context. + * + * @param {string} indexName + * @param {InstantSearchParams} params + * @param {InstantMeiliSearchOptions={}} meiliSearchOptions + * @param {MeiliSearch} MeiliSearchClient + * @returns {SearchContext} + */ +function createContext( + indexName: string, + params: InstantSearchParams, + meiliSearchOptions: InstantMeiliSearchOptions = {} +): SearchContext { + const { + paginationTotalHits, + primaryKey, + placeholderSearch, + } = meiliSearchOptions + + const page = params?.page + const hitsPerPage = params?.hitsPerPage + + const query = params?.query + // Split index name and possible sorting rules + const [indexUid, ...sortByArray] = indexName.split(':') + + const context = { + indexUid: indexUid, + paginationTotalHits: paginationTotalHits || 200, + primaryKey: primaryKey || undefined, + placeholderSearch: placeholderSearch !== false, // true by default + hitsPerPage: hitsPerPage === undefined ? 20 : hitsPerPage, // 20 is the MeiliSearch's default limit value. `hitsPerPage` can be changed with `InsantSearch.configure`. + page: page || 0, // default page is 0 if none is provided + sort: sortByArray.join(':') || '', + query, + } + return context +} + +/** + * Instanciate SearchClient required by instantsearch.js. + * + * @param {string} hostUrl + * @param {string} apiKey + * @param {InstantMeiliSearchOptions={}} meiliSearchOptions + * @returns {InstantMeiliSearchInstance} + */ +export function instantMeiliSearch( + hostUrl: string, + apiKey: string, + meiliSearchOptions: InstantMeiliSearchOptions = {} +): InstantMeiliSearchInstance { + const searchResolver = SearchResolver(ResponseCache()) + return { + MeiliSearchClient: new MeiliSearch({ host: hostUrl, apiKey: apiKey }), + search: async function >( + instantSearchRequests: readonly AlgoliaMultipleQueriesQuery[] + // options?: RequestOptions & MultipleQueriesOptions - When is this used ? + ): Promise<{ results: Array> }> { + try { + const searchRequest = instantSearchRequests[0] + const { params: instantSearchParams } = searchRequest + + const context = createContext( + searchRequest.indexName, + instantSearchParams, + meiliSearchOptions + ) + + const searchResponse = await searchResolver.searchResponse( + context, + instantSearchParams, + this.MeiliSearchClient + ) + + // Adapt the MeiliSearch responsne to a compliant instantsearch.js response + const adaptedSearchResponse = adaptSearchResponse( + context.indexUid, + searchResponse, + instantSearchParams, + context + ) + return adaptedSearchResponse + } catch (e: any) { + console.error(e) + throw new Error(e) + } + }, + searchForFacetValues: async function (_) { + return await new Promise((resolve, reject) => { + reject( + new Error('SearchForFacetValues is not compatible with MeiliSearch') + ) + resolve([]) // added here to avoid compilation error + }) + }, + } +} diff --git a/src/client/search-resolver.ts b/src/client/search-resolver.ts new file mode 100644 index 00000000..498a4bc3 --- /dev/null +++ b/src/client/search-resolver.ts @@ -0,0 +1,60 @@ +import { adaptSearchRequest, adaptFacetsDistribution } from '..' +import { + SearchContext, + InstantSearchParams, + MeiliSearch, + MeiliSearchResponse, + ResponseCacher, +} from '../types' +import { cacheFilters } from '../cache' + +/** + * @param {ResponseCacher} cache + */ +export function SearchResolver(cache: ResponseCacher) { + return { + cache, + /** + * @param {SearchContext} searchContext + * @param {InstantSearchParams} instantsearchParams + * @param {MeiliSearch} client + * @returns {Promise>>} + */ + searchResponse: async function ( + searchContext: SearchContext, + instantsearchParams: InstantSearchParams, + client: MeiliSearch + ): Promise>> { + const key = cache.createKey([searchContext, instantsearchParams]) + const response = cache.getCachedValue(key) + if (response) return response + + // Adapt search request to MeiliSearch compliant search request + const adaptedSearchRequest = adaptSearchRequest( + instantsearchParams, + searchContext.paginationTotalHits, + searchContext.placeholderSearch, + searchContext.sort, + searchContext.query + ) + + // Cache filters + const filterCache = cacheFilters(adaptedSearchRequest?.filter) + + // Make search request + const searchResponse = await client + .index(searchContext.indexUid) + .search(searchContext.query, adaptedSearchRequest) + + // Add facets back into facetsDistribution + searchResponse.facetsDistribution = adaptFacetsDistribution( + filterCache, + searchResponse.facetsDistribution + ) + + // Cache response + cache.populate(searchResponse, key) + return searchResponse + }, + } +} diff --git a/src/client/search.ts b/src/client/search.ts new file mode 100644 index 00000000..cf15eb02 --- /dev/null +++ b/src/client/search.ts @@ -0,0 +1,15 @@ +import { MeiliSearch } from 'meilisearch' +import { SearchContext, MeiliSearchParams, MeiliSearchResponse } from '..' +/** + * @param {MeiliSearch} client + * @param {SearchContext} context + * @param {MeiliSearchParams} searchRequest + * @returns {Promise} + */ +export async function search( + client: MeiliSearch, + context: SearchContext, + searchRequest: MeiliSearchParams +): Promise { + return client.index(context.indexUid).search(context.query, searchRequest) +} diff --git a/src/types/types.ts b/src/types/types.ts index 78f71c1b..0e005b34 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,4 +1,7 @@ -import type { MeiliSearch } from 'meilisearch' +import type { + MeiliSearch, + SearchResponse as MeiliSearchResponse, +} from 'meilisearch' import type { SearchClient } from 'instantsearch.js' import type { SearchOptions as AlgoliaSearchOptions, @@ -12,11 +15,12 @@ export type { FacetsDistribution, SearchResponse as MeiliSearchResponse, SearchParams as MeiliSearchParams, + MeiliSearch, } from 'meilisearch' export type InstantSearchParams = AlgoliaMultipleQueriesQuery['params'] -export type Cache = { +export type FilterCache = { [category: string]: string[] } @@ -31,12 +35,17 @@ export type InstantMeiliSearchOptions = { primaryKey?: string } +export type ResponseCacher = { + getCachedValue: (key: string) => MeiliSearchResponse | undefined + createKey: (components: any[]) => string + populate: (searchResponse: MeiliSearchResponse, key: string) => void +} + export type SearchContext = { page: number paginationTotalHits: number hitsPerPage: number primaryKey?: string - client: MeiliSearch placeholderSearch: boolean sort?: string query?: string diff --git a/src/utils/string.ts b/src/utils/string.ts index fc16bb14..cfe8cfe4 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -1,6 +1,6 @@ /** * @param {any} str - * @returns boolean + * @returns {boolean} */ export function isString(str: any): boolean { return typeof str === 'string' || str instanceof String @@ -8,9 +8,19 @@ export function isString(str: any): boolean { /** * @param {string} filter - * @returns string + * @returns {string} */ export function replaceColonByEqualSign(filter: string): string { // will only change first occurence of `:` return filter.replace(/:(.*)/i, '="$1"') } + +/** + * @param {any[]} arr + * @returns {string} + */ +export function stringifyArray(arr: any[]): string { + return arr.reduce((acc: string, curr: any) => { + return (acc += ` ${JSON.stringify(curr)}`) + }, '') +} From 258e488ca2e6d1487001f078a938210c342be7bc Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Mon, 27 Sep 2021 21:44:51 +0200 Subject: [PATCH 02/17] Add tests to avoid failing test --- src/client/__tests__/cache.tests.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/__tests__/cache.tests.ts b/src/client/__tests__/cache.tests.ts index ba6a70c3..e2112b12 100644 --- a/src/client/__tests__/cache.tests.ts +++ b/src/client/__tests__/cache.tests.ts @@ -1,9 +1,9 @@ // import { SearchResolver } from '..' -// describe('Search Resolver test', () => { -// test('Cached value', async () => { - -// }) -// }) +describe('Search Resolver test', () => { + test('Cached value', () => { + console.log('todo') + }) +}) // TODO From 0ff08357fc841a367d4eec894bd5d4e90513e0bb Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 28 Sep 2021 01:31:57 +0200 Subject: [PATCH 03/17] Use cached results even on pagination changes --- src/adapter/hits-adapter.ts | 4 +- src/adapter/search-response-adapter.ts | 3 +- src/cache/search-response.ts | 6 +- src/client/__tests__/cache.tests.ts | 9 -- src/client/instant-meilisearch-client.ts | 2 +- src/client/search-resolver.ts | 22 +++-- src/client/search.ts | 2 +- src/types/instantsearch-types.ts | 117 ----------------------- src/types/types.ts | 7 +- 9 files changed, 26 insertions(+), 146 deletions(-) delete mode 100644 src/client/__tests__/cache.tests.ts delete mode 100644 src/types/instantsearch-types.ts diff --git a/src/adapter/hits-adapter.ts b/src/adapter/hits-adapter.ts index bae4496e..3c962708 100644 --- a/src/adapter/hits-adapter.ts +++ b/src/adapter/hits-adapter.ts @@ -13,8 +13,8 @@ export function adaptHits( instantSearchParams: InstantSearchParams, instantMeiliSearchContext: SearchContext ): any { - const { primaryKey } = instantMeiliSearchContext - const { page, hitsPerPage } = instantMeiliSearchContext + const { hitsPerPage, primaryKey } = instantMeiliSearchContext + const page = instantSearchParams?.page || 0 const paginatedHits = adaptPagination(meiliSearchHits, page, hitsPerPage) return paginatedHits.map((hit: any) => { diff --git a/src/adapter/search-response-adapter.ts b/src/adapter/search-response-adapter.ts index ebc7596c..a633d077 100644 --- a/src/adapter/search-response-adapter.ts +++ b/src/adapter/search-response-adapter.ts @@ -48,7 +48,8 @@ export function adaptSearchResponse( const processingTimeMs = searchResponse.processingTimeMs const query = searchResponse.query - const { hitsPerPage, page } = instantMeiliSearchContext + const { hitsPerPage } = instantMeiliSearchContext + const page = instantSearchParams?.page || 0 // Create response object compliant with InstantSearch const adaptedSearchResponse = { diff --git a/src/cache/search-response.ts b/src/cache/search-response.ts index 454ff468..c9c1ac66 100644 --- a/src/cache/search-response.ts +++ b/src/cache/search-response.ts @@ -9,14 +9,14 @@ export function ResponseCache( ): ResponseCacher { const searchCache = cache return { - getCachedValue: function (key: string) { + getEntry: function (key: string) { if (searchCache[key]) return JSON.parse(searchCache[key]) return undefined }, - createKey: function (components: any[]) { + formatKey: function (components: any[]) { return stringifyArray(components) }, - populate: function (searchResponse: MeiliSearchResponse, key: string) { + setEntry: function (searchResponse: MeiliSearchResponse, key: string) { searchCache[key] = JSON.stringify(searchResponse) }, } diff --git a/src/client/__tests__/cache.tests.ts b/src/client/__tests__/cache.tests.ts deleted file mode 100644 index e2112b12..00000000 --- a/src/client/__tests__/cache.tests.ts +++ /dev/null @@ -1,9 +0,0 @@ -// import { SearchResolver } from '..' - -describe('Search Resolver test', () => { - test('Cached value', () => { - console.log('todo') - }) -}) - -// TODO diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index d9ac7c6a..e81f66ac 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -8,7 +8,7 @@ import { SearchContext, } from '../types' import { adaptSearchResponse } from '../adapter' -import { SearchResolver } from '.' +import { SearchResolver } from './search-resolver' import { ResponseCache } from '../cache' /** diff --git a/src/client/search-resolver.ts b/src/client/search-resolver.ts index 498a4bc3..37cbb333 100644 --- a/src/client/search-resolver.ts +++ b/src/client/search-resolver.ts @@ -1,4 +1,4 @@ -import { adaptSearchRequest, adaptFacetsDistribution } from '..' +import { adaptSearchRequest, adaptFacetsDistribution } from '../adapter' import { SearchContext, InstantSearchParams, @@ -13,7 +13,6 @@ import { cacheFilters } from '../cache' */ export function SearchResolver(cache: ResponseCacher) { return { - cache, /** * @param {SearchContext} searchContext * @param {InstantSearchParams} instantsearchParams @@ -25,10 +24,6 @@ export function SearchResolver(cache: ResponseCacher) { instantsearchParams: InstantSearchParams, client: MeiliSearch ): Promise>> { - const key = cache.createKey([searchContext, instantsearchParams]) - const response = cache.getCachedValue(key) - if (response) return response - // Adapt search request to MeiliSearch compliant search request const adaptedSearchRequest = adaptSearchRequest( instantsearchParams, @@ -38,7 +33,18 @@ export function SearchResolver(cache: ResponseCacher) { searchContext.query ) - // Cache filters + // Create key with relevant informations + const key = cache.formatKey([ + adaptedSearchRequest, + searchContext.indexUid, + searchContext.query, + ]) + const entry = cache.getEntry(key) + + // Request is cached. + if (entry) return entry + + // Cache filters: todo components const filterCache = cacheFilters(adaptedSearchRequest?.filter) // Make search request @@ -53,7 +59,7 @@ export function SearchResolver(cache: ResponseCacher) { ) // Cache response - cache.populate(searchResponse, key) + cache.setEntry(searchResponse, key) return searchResponse }, } diff --git a/src/client/search.ts b/src/client/search.ts index cf15eb02..adab5867 100644 --- a/src/client/search.ts +++ b/src/client/search.ts @@ -1,5 +1,5 @@ import { MeiliSearch } from 'meilisearch' -import { SearchContext, MeiliSearchParams, MeiliSearchResponse } from '..' +import { SearchContext, MeiliSearchParams, MeiliSearchResponse } from '../types' /** * @param {MeiliSearch} client * @param {SearchContext} context diff --git a/src/types/instantsearch-types.ts b/src/types/instantsearch-types.ts deleted file mode 100644 index d855aa5a..00000000 --- a/src/types/instantsearch-types.ts +++ /dev/null @@ -1,117 +0,0 @@ -export type GeoRectangle = [number, number, number, number] -export type GeoPolygon = [number, number, number, number, number, number] - -// Documentation: https://www.algolia.com/doc/api-reference/search-api-parameters/ -export type SearchParameters = { - // Attributes - attributesToRetrieve?: string[] - restrictSearchableAttributes?: string[] - - // Filtering - filters?: string - facetFilters?: string[] - optionalFilters?: string[] - numericFilters?: string[] - sumOrFiltersScores?: boolean - - // Faceting - facets?: string[] - maxValuesPerFacet?: number - facetingAfterDistinct?: boolean - sortFacetValuesBy?: string - - // Highlighting / Snippeting - attributesToHighlight?: string[] - attributesToSnippet?: string[] - highlightPreTag?: string - highlightPostTag?: string - snippetEllipsisText?: string - restrictHighlightAndSnippetArrays?: boolean - - // Pagination - page?: number - hitsPerPage?: number - offset?: number - length?: number - - // Typos - minWordSizefor1Typo?: number - minWordSizefor2Typos?: number - typoTolerance?: string | boolean - allowTyposOnNumericTokens?: boolean - ignorePlurals?: boolean | string[] - disableTypoToleranceOnAttributes?: string[] - - // Geo-Search - aroundLatLng?: string - aroundLatLngViaIP?: boolean - aroundRadius?: number | 'all' - aroundPrecision?: number - minimumAroundRadius?: number - insideBoundingBox?: GeoRectangle | GeoRectangle[] - insidePolygon?: GeoPolygon | GeoPolygon[] - - // Query Strategy - queryType?: string - removeWordsIfNoResults?: string - advancedSyntax?: boolean - optionalWords?: string | string[] - removeStopWords?: boolean | string[] - disableExactOnAttributes?: string[] - exactOnSingleWordQuery?: string - alternativesAsExact?: string[] - - // Query Rules - enableRules?: boolean - ruleContexts?: string[] - - // Advanced - minProximity?: number - responseFields?: string[] - maxFacetHits?: number - percentileComputation?: boolean - distinct?: number | boolean - getRankingInfo?: boolean - clickAnalytics?: boolean - analytics?: boolean - analyticsTags?: string[] - synonyms?: boolean - replaceSynonymsInHighlight?: boolean -} - -export interface SearchRequestParameters extends SearchParameters { - query: string -} - -export interface SearchForFacetValuesRequestParameters - extends SearchParameters { - facetQuery: string - facetName: string -} - -export type SearchRequest = { - indexName: string - params: SearchRequestParameters -} - -export type Hit> = T & { - [key: string]: any - _highlightResult?: Record -} - -// Documentation: https://www.algolia.com/doc/rest-api/search/?language=javascript#search-multiple-indexes -export type SearchResponse = { - hits: Hit[] - page?: number - nbHits?: number - nbPages?: number - hitsPerPage?: number - processingTimeMS?: number - query?: string - params?: string - index?: string -} - -export type SearchClient = { - search: (requests: SearchRequest[]) => Promise<{ results: SearchResponse[] }> -} diff --git a/src/types/types.ts b/src/types/types.ts index 0e005b34..119973bd 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -36,13 +36,12 @@ export type InstantMeiliSearchOptions = { } export type ResponseCacher = { - getCachedValue: (key: string) => MeiliSearchResponse | undefined - createKey: (components: any[]) => string - populate: (searchResponse: MeiliSearchResponse, key: string) => void + getEntry: (key: string) => MeiliSearchResponse | undefined + formatKey: (components: any[]) => string + setEntry: (searchResponse: MeiliSearchResponse, key: string) => void } export type SearchContext = { - page: number paginationTotalHits: number hitsPerPage: number primaryKey?: string From 8b91813989b8abea3a58c74150b5d668175f8f8a Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 28 Sep 2021 11:20:32 +0200 Subject: [PATCH 04/17] Add component architecture --- src/adapter/__tests__/utils.ts | 13 ------- src/adapter/facets-distribution-adapter.ts | 35 ----------------- src/adapter/index.ts | 4 -- .../facets-distribution-assigns.tests.ts} | 27 ++++++------- .../__tests__/filter-cache.tests.ts} | 2 +- .../filter-adapter.ts | 4 +- .../search-request-adapter}/filters.ts | 39 ++++++++++++++++++- src/adapter/search-request-adapter/index.ts | 2 + .../search-params-adapter.ts} | 4 +- .../search-resolver.ts | 30 +++++--------- .../search-request-adapter}/search.ts | 8 +++- .../__tests__/pagination-adapter.tests.ts | 4 +- .../highlight-adapter.ts | 8 ++-- .../hits-adapter.ts | 5 +-- src/adapter/search-response-adapter/index.ts | 1 + .../pagination-adapter.ts | 0 .../search-response-adapter.ts | 18 ++++----- src/cache/index.ts | 3 +- .../{search-response.ts => search-cache.ts} | 8 ++-- src/client/index.ts | 2 - src/client/instant-meilisearch-client.ts | 29 +++++++++----- src/index.ts | 1 - src/types/types.ts | 3 +- 23 files changed, 116 insertions(+), 134 deletions(-) delete mode 100644 src/adapter/__tests__/utils.ts delete mode 100644 src/adapter/facets-distribution-adapter.ts rename src/adapter/{__tests__/facets-distribution-adapter.tests.ts => search-request-adapter/__tests__/facets-distribution-assigns.tests.ts} (74%) rename src/{cache/__tests__/cache.tests.ts => adapter/search-request-adapter/__tests__/filter-cache.tests.ts} (98%) rename src/adapter/{ => search-request-adapter}/filter-adapter.ts (95%) rename src/{cache => adapter/search-request-adapter}/filters.ts (54%) create mode 100644 src/adapter/search-request-adapter/index.ts rename src/adapter/{search-request-adapter.ts => search-request-adapter/search-params-adapter.ts} (97%) rename src/{client => adapter/search-request-adapter}/search-resolver.ts (58%) rename src/{client => adapter/search-request-adapter}/search.ts (76%) rename src/adapter/{ => search-response-adapter}/__tests__/pagination-adapter.tests.ts (97%) rename src/adapter/{ => search-response-adapter}/highlight-adapter.ts (96%) rename src/adapter/{ => search-response-adapter}/hits-adapter.ts (84%) create mode 100644 src/adapter/search-response-adapter/index.ts rename src/adapter/{ => search-response-adapter}/pagination-adapter.ts (100%) rename src/adapter/{ => search-response-adapter}/search-response-adapter.ts (81%) rename src/cache/{search-response.ts => search-cache.ts} (77%) diff --git a/src/adapter/__tests__/utils.ts b/src/adapter/__tests__/utils.ts deleted file mode 100644 index 67852736..00000000 --- a/src/adapter/__tests__/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MeiliSearch } from 'meilisearch' -const HOST = 'http://localhost:7700' - -const defaultContext = { - client: new MeiliSearch({ host: HOST }), - paginationTotalHits: 200, - primaryKey: undefined, - placeholderSearch: true, - hitsPerPage: 20, - page: 0, -} - -export { defaultContext } diff --git a/src/adapter/facets-distribution-adapter.ts b/src/adapter/facets-distribution-adapter.ts deleted file mode 100644 index c3a3880e..00000000 --- a/src/adapter/facets-distribution-adapter.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { FacetsDistribution, FilterCache } from '../types' - -/** - * Adapt MeiliSearch facetsDistribution to instantsearch.js facetsDistribution - * by completing the list of distribution with the facets that are checked in the components. - * - * To be aware of which field are checked a cache is provided that was made prior of the search request. - * - * @param {FilterCache} cache? - * @param {FacetsDistribution} distribution? - * @returns {FacetsDistribution} - */ -export function adaptFacetsDistribution( - cache?: FilterCache, - distribution?: FacetsDistribution -): FacetsDistribution { - distribution = distribution || {} - if (cache && Object.keys(cache).length > 0) { - for (const cachedFacet in cache) { - for (const cachedField of cache[cachedFacet]) { - // if cached field is not present in the returned distribution - - if ( - !distribution[cachedFacet] || - !Object.keys(distribution[cachedFacet]).includes(cachedField) - ) { - // add 0 value - distribution[cachedFacet] = distribution[cachedFacet] || {} - distribution[cachedFacet][cachedField] = 0 - } - } - } - } - return distribution -} diff --git a/src/adapter/index.ts b/src/adapter/index.ts index 5bda2672..ea566b4a 100644 --- a/src/adapter/index.ts +++ b/src/adapter/index.ts @@ -1,6 +1,2 @@ export * from './search-request-adapter' export * from './search-response-adapter' -export * from './hits-adapter' -export * from './highlight-adapter' -export * from './pagination-adapter' -export * from './facets-distribution-adapter' diff --git a/src/adapter/__tests__/facets-distribution-adapter.tests.ts b/src/adapter/search-request-adapter/__tests__/facets-distribution-assigns.tests.ts similarity index 74% rename from src/adapter/__tests__/facets-distribution-adapter.tests.ts rename to src/adapter/search-request-adapter/__tests__/facets-distribution-assigns.tests.ts index 341407ea..650065a0 100644 --- a/src/adapter/__tests__/facets-distribution-adapter.tests.ts +++ b/src/adapter/search-request-adapter/__tests__/facets-distribution-assigns.tests.ts @@ -1,7 +1,7 @@ -import { adaptFacetsDistribution } from '../' +import { assignMissingFilters } from '../filters' test('One field in cache present in distribution', () => { - const returnedDistribution = adaptFacetsDistribution( + const returnedDistribution = assignMissingFilters( { genre: ['comedy'] }, { genre: { comedy: 1 } } ) @@ -9,15 +9,12 @@ test('One field in cache present in distribution', () => { }) test('One field in cache not present in distribution', () => { - const returnedDistribution = adaptFacetsDistribution( - { genre: ['comedy'] }, - {} - ) + const returnedDistribution = assignMissingFilters({ genre: ['comedy'] }, {}) expect(returnedDistribution).toMatchObject({ genre: { comedy: 0 } }) }) test('two field in cache only one present in distribution', () => { - const returnedDistribution = adaptFacetsDistribution( + const returnedDistribution = assignMissingFilters( { genre: ['comedy'], title: ['hamlet'] }, { genre: { comedy: 12 } } ) @@ -28,7 +25,7 @@ test('two field in cache only one present in distribution', () => { }) test('two field in cache w/ different facet name none present in distribution', () => { - const returnedDistribution = adaptFacetsDistribution( + const returnedDistribution = assignMissingFilters( { genre: ['comedy'], title: ['hamlet'] }, {} ) @@ -39,7 +36,7 @@ test('two field in cache w/ different facet name none present in distribution', }) test('two field in cache w/ different facet name both present in distribution', () => { - const returnedDistribution = adaptFacetsDistribution( + const returnedDistribution = assignMissingFilters( { genre: ['comedy'], title: ['hamlet'] }, { genre: { comedy: 12 }, title: { hamlet: 1 } } ) @@ -50,7 +47,7 @@ test('two field in cache w/ different facet name both present in distribution', }) test('Three field in cache w/ different facet name two present in distribution', () => { - const returnedDistribution = adaptFacetsDistribution( + const returnedDistribution = assignMissingFilters( { genre: ['comedy', 'horror'], title: ['hamlet'] }, { genre: { comedy: 12 }, title: { hamlet: 1 } } ) @@ -61,14 +58,14 @@ test('Three field in cache w/ different facet name two present in distribution', }) test('Cache is undefined and facets distribution is not', () => { - const returnedDistribution = adaptFacetsDistribution(undefined, { + const returnedDistribution = assignMissingFilters(undefined, { genre: { comedy: 12 }, }) expect(returnedDistribution).toMatchObject({ genre: { comedy: 12 } }) }) test('Cache is empty object and facets distribution is not', () => { - const returnedDistribution = adaptFacetsDistribution( + const returnedDistribution = assignMissingFilters( {}, { genre: { comedy: 12 } } ) @@ -76,16 +73,16 @@ test('Cache is empty object and facets distribution is not', () => { }) test('Cache is empty object and facets distribution empty object', () => { - const returnedDistribution = adaptFacetsDistribution({}, {}) + const returnedDistribution = assignMissingFilters({}, {}) expect(returnedDistribution).toMatchObject({}) }) test('Cache is undefined and facets distribution empty object', () => { - const returnedDistribution = adaptFacetsDistribution(undefined, {}) + const returnedDistribution = assignMissingFilters(undefined, {}) expect(returnedDistribution).toMatchObject({}) }) test('Cache is undefined and facets distribution is undefined', () => { - const returnedDistribution = adaptFacetsDistribution(undefined, undefined) + const returnedDistribution = assignMissingFilters(undefined, undefined) expect(returnedDistribution).toMatchObject({}) }) diff --git a/src/cache/__tests__/cache.tests.ts b/src/adapter/search-request-adapter/__tests__/filter-cache.tests.ts similarity index 98% rename from src/cache/__tests__/cache.tests.ts rename to src/adapter/search-request-adapter/__tests__/filter-cache.tests.ts index b2cd9eed..8687007d 100644 --- a/src/cache/__tests__/cache.tests.ts +++ b/src/adapter/search-request-adapter/__tests__/filter-cache.tests.ts @@ -1,4 +1,4 @@ -import { cacheFilters } from '..' +import { cacheFilters } from '../filters' const facetCacheData = [ { diff --git a/src/adapter/filter-adapter.ts b/src/adapter/search-request-adapter/filter-adapter.ts similarity index 95% rename from src/adapter/filter-adapter.ts rename to src/adapter/search-request-adapter/filter-adapter.ts index cb6d7d5a..5e2e5e5b 100644 --- a/src/adapter/filter-adapter.ts +++ b/src/adapter/search-request-adapter/filter-adapter.ts @@ -1,5 +1,5 @@ -import type { Filter, AlgoliaSearchOptions } from '../types' -import { replaceColonByEqualSign } from '../utils' +import type { Filter, AlgoliaSearchOptions } from '../../types' +import { replaceColonByEqualSign } from '../../utils' /** * Transform InstantSearch filter to MeiliSearch filter. diff --git a/src/cache/filters.ts b/src/adapter/search-request-adapter/filters.ts similarity index 54% rename from src/cache/filters.ts rename to src/adapter/search-request-adapter/filters.ts index db7a9e15..74b641a6 100644 --- a/src/cache/filters.ts +++ b/src/adapter/search-request-adapter/filters.ts @@ -1,5 +1,5 @@ -import { Filter, FilterCache, ParsedFilter } from '../types' -import { removeUndefined } from '../utils' +import { Filter, ParsedFilter, FacetsDistribution, FilterCache } from '../../types' +import { removeUndefined } from '../../utils' /** * @param {string} filter @@ -53,3 +53,38 @@ export function cacheFilters(filters?: Filter): FilterCache { {} as FilterCache ) } + + +/** + * Assign missing filters to facetsDistribution. + * All facet passed as filter should appear in the facetsDistribution. + * If not present, the facet is added with 0 as value. + * + * + * @param {FilterCache} cache? + * @param {FacetsDistribution} distribution? + * @returns {FacetsDistribution} + */ +export function assignMissingFilters( + cache?: FilterCache, + distribution?: FacetsDistribution +): FacetsDistribution { + distribution = distribution || {} + if (cache && Object.keys(cache).length > 0) { + for (const cachedFacet in cache) { + for (const cachedField of cache[cachedFacet]) { + // if cached field is not present in the returned distribution + + if ( + !distribution[cachedFacet] || + !Object.keys(distribution[cachedFacet]).includes(cachedField) + ) { + // add 0 value + distribution[cachedFacet] = distribution[cachedFacet] || {} + distribution[cachedFacet][cachedField] = 0 + } + } + } + } + return distribution +} diff --git a/src/adapter/search-request-adapter/index.ts b/src/adapter/search-request-adapter/index.ts new file mode 100644 index 00000000..4d77cda1 --- /dev/null +++ b/src/adapter/search-request-adapter/index.ts @@ -0,0 +1,2 @@ +export * from './search-resolver' +export * from './search-params-adapter' diff --git a/src/adapter/search-request-adapter.ts b/src/adapter/search-request-adapter/search-params-adapter.ts similarity index 97% rename from src/adapter/search-request-adapter.ts rename to src/adapter/search-request-adapter/search-params-adapter.ts index cecd6aad..800de22e 100644 --- a/src/adapter/search-request-adapter.ts +++ b/src/adapter/search-request-adapter/search-params-adapter.ts @@ -1,4 +1,4 @@ -import type { InstantSearchParams, MeiliSearchParams } from '../types' +import type { InstantSearchParams, MeiliSearchParams } from '../../types' import { adaptFilters } from './filter-adapter' @@ -13,7 +13,7 @@ import { adaptFilters } from './filter-adapter' * @param {string} query? * @returns MeiliSearchParams */ -export function adaptSearchRequest( +export function adaptSearchParams( instantSearchParams: InstantSearchParams, paginationTotalHits: number, placeholderSearch: boolean, diff --git a/src/client/search-resolver.ts b/src/adapter/search-request-adapter/search-resolver.ts similarity index 58% rename from src/client/search-resolver.ts rename to src/adapter/search-request-adapter/search-resolver.ts index 37cbb333..a8be8dd2 100644 --- a/src/client/search-resolver.ts +++ b/src/adapter/search-request-adapter/search-resolver.ts @@ -1,17 +1,16 @@ -import { adaptSearchRequest, adaptFacetsDistribution } from '../adapter' import { SearchContext, - InstantSearchParams, MeiliSearch, MeiliSearchResponse, - ResponseCacher, -} from '../types' -import { cacheFilters } from '../cache' + SearchCacheInterface, + MeiliSearchParams, +} from '../../types' +import { cacheFilters, assignMissingFilters } from './filters' /** * @param {ResponseCacher} cache */ -export function SearchResolver(cache: ResponseCacher) { +export function SearchResolver(cache: SearchCacheInterface) { return { /** * @param {SearchContext} searchContext @@ -21,21 +20,12 @@ export function SearchResolver(cache: ResponseCacher) { */ searchResponse: async function ( searchContext: SearchContext, - instantsearchParams: InstantSearchParams, + searchParams: MeiliSearchParams, client: MeiliSearch ): Promise>> { - // Adapt search request to MeiliSearch compliant search request - const adaptedSearchRequest = adaptSearchRequest( - instantsearchParams, - searchContext.paginationTotalHits, - searchContext.placeholderSearch, - searchContext.sort, - searchContext.query - ) - // Create key with relevant informations const key = cache.formatKey([ - adaptedSearchRequest, + searchParams, searchContext.indexUid, searchContext.query, ]) @@ -45,15 +35,15 @@ export function SearchResolver(cache: ResponseCacher) { if (entry) return entry // Cache filters: todo components - const filterCache = cacheFilters(adaptedSearchRequest?.filter) + const filterCache = cacheFilters(searchParams?.filter) // Make search request const searchResponse = await client .index(searchContext.indexUid) - .search(searchContext.query, adaptedSearchRequest) + .search(searchContext.query, searchParams) // Add facets back into facetsDistribution - searchResponse.facetsDistribution = adaptFacetsDistribution( + searchResponse.facetsDistribution = assignMissingFilters( filterCache, searchResponse.facetsDistribution ) diff --git a/src/client/search.ts b/src/adapter/search-request-adapter/search.ts similarity index 76% rename from src/client/search.ts rename to src/adapter/search-request-adapter/search.ts index adab5867..6dbc866b 100644 --- a/src/client/search.ts +++ b/src/adapter/search-request-adapter/search.ts @@ -1,5 +1,9 @@ -import { MeiliSearch } from 'meilisearch' -import { SearchContext, MeiliSearchParams, MeiliSearchResponse } from '../types' +import { + SearchContext, + MeiliSearchParams, + MeiliSearchResponse, + MeiliSearch, +} from '../../types' /** * @param {MeiliSearch} client * @param {SearchContext} context diff --git a/src/adapter/__tests__/pagination-adapter.tests.ts b/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts similarity index 97% rename from src/adapter/__tests__/pagination-adapter.tests.ts rename to src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts index da4de9dc..8388f5a6 100644 --- a/src/adapter/__tests__/pagination-adapter.tests.ts +++ b/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts @@ -1,5 +1,5 @@ -import { adaptPagination } from '..' -import { ceiledDivision } from '../../utils' +import { adaptPagination } from '../pagination-adapter' +import { ceiledDivision } from '../../../utils' const numberPagesTestParameters = [ { diff --git a/src/adapter/highlight-adapter.ts b/src/adapter/search-response-adapter/highlight-adapter.ts similarity index 96% rename from src/adapter/highlight-adapter.ts rename to src/adapter/search-response-adapter/highlight-adapter.ts index bd5d4680..e0a30603 100644 --- a/src/adapter/highlight-adapter.ts +++ b/src/adapter/search-response-adapter/highlight-adapter.ts @@ -1,5 +1,5 @@ -import { isString } from '../utils' -import { InstantSearchParams } from '../types' +import { isString } from '../../utils' +import { InstantSearchParams } from '../../types' /** * Replace `em` tags in highlighted MeiliSearch hits to @@ -42,7 +42,7 @@ function adaptHighlight( // formattedHit is the `_formatted` object returned by MeiliSearch. // It contains all the highlighted and croped attributes return Object.keys(formattedHit).reduce((result, key) => { - ;(result[key] as any) = { + ; (result[key] as any) = { value: replaceHighlightTags( formattedHit[key], highlightPreTag, @@ -108,7 +108,7 @@ function adaptSnippet( // It contains all the highlighted and croped attributes return (Object.keys(formattedHit) as any[]).reduce((result, key) => { if (attributesToSnippet?.includes(key)) { - ;(result[key] as any) = { + ; (result[key] as any) = { value: snippetValue( formattedHit[key], snippetEllipsisText, diff --git a/src/adapter/hits-adapter.ts b/src/adapter/search-response-adapter/hits-adapter.ts similarity index 84% rename from src/adapter/hits-adapter.ts rename to src/adapter/search-response-adapter/hits-adapter.ts index 3c962708..9243d908 100644 --- a/src/adapter/hits-adapter.ts +++ b/src/adapter/search-response-adapter/hits-adapter.ts @@ -1,4 +1,4 @@ -import type { InstantSearchParams, SearchContext } from '../types' +import type { InstantSearchParams, SearchContext } from '../../types' import { adaptPagination } from './pagination-adapter' import { adaptFormating } from './highlight-adapter' @@ -13,8 +13,7 @@ export function adaptHits( instantSearchParams: InstantSearchParams, instantMeiliSearchContext: SearchContext ): any { - const { hitsPerPage, primaryKey } = instantMeiliSearchContext - const page = instantSearchParams?.page || 0 + const { hitsPerPage, primaryKey, page } = instantMeiliSearchContext const paginatedHits = adaptPagination(meiliSearchHits, page, hitsPerPage) return paginatedHits.map((hit: any) => { diff --git a/src/adapter/search-response-adapter/index.ts b/src/adapter/search-response-adapter/index.ts new file mode 100644 index 00000000..e1584f52 --- /dev/null +++ b/src/adapter/search-response-adapter/index.ts @@ -0,0 +1 @@ +export * from './search-response-adapter' diff --git a/src/adapter/pagination-adapter.ts b/src/adapter/search-response-adapter/pagination-adapter.ts similarity index 100% rename from src/adapter/pagination-adapter.ts rename to src/adapter/search-response-adapter/pagination-adapter.ts diff --git a/src/adapter/search-response-adapter.ts b/src/adapter/search-response-adapter/search-response-adapter.ts similarity index 81% rename from src/adapter/search-response-adapter.ts rename to src/adapter/search-response-adapter/search-response-adapter.ts index a633d077..105e7b83 100644 --- a/src/adapter/search-response-adapter.ts +++ b/src/adapter/search-response-adapter/search-response-adapter.ts @@ -3,8 +3,8 @@ import type { InstantSearchParams, MeiliSearchResponse, AlgoliaSearchResponse, -} from '../types' -import { ceiledDivision } from '../utils' +} from '../../types' +import { ceiledDivision } from '../../utils' import { adaptHits } from './hits-adapter' /** @@ -14,14 +14,13 @@ import { adaptHits } from './hits-adapter' * @param {string} indexUid * @param {MeiliSearchResponse( - indexUid: string, searchResponse: MeiliSearchResponse>, instantSearchParams: InstantSearchParams, - instantMeiliSearchContext: SearchContext + searchContext: SearchContext ): { results: Array> } { const searchResponseOptionals: Record = {} @@ -35,12 +34,12 @@ export function adaptSearchResponse( const hits = adaptHits( searchResponse.hits, instantSearchParams, - instantMeiliSearchContext + searchContext ) const nbPages = ceiledDivision( searchResponse.hits.length, - instantMeiliSearchContext.hitsPerPage + searchContext.hitsPerPage ) const exhaustiveNbHits = searchResponse.exhaustiveNbHits @@ -48,12 +47,11 @@ export function adaptSearchResponse( const processingTimeMs = searchResponse.processingTimeMs const query = searchResponse.query - const { hitsPerPage } = instantMeiliSearchContext - const page = instantSearchParams?.page || 0 + const { hitsPerPage, page } = searchContext // Create response object compliant with InstantSearch const adaptedSearchResponse = { - index: indexUid, + index: searchContext.indexUid, hitsPerPage, page, facets, diff --git a/src/cache/index.ts b/src/cache/index.ts index 42c39ed8..891215f0 100644 --- a/src/cache/index.ts +++ b/src/cache/index.ts @@ -1,2 +1 @@ -export * from './filters' -export * from './search-response' +export * from './search-cache' diff --git a/src/cache/search-response.ts b/src/cache/search-cache.ts similarity index 77% rename from src/cache/search-response.ts rename to src/cache/search-cache.ts index c9c1ac66..e2c81ab7 100644 --- a/src/cache/search-response.ts +++ b/src/cache/search-cache.ts @@ -1,12 +1,12 @@ -import { ResponseCacher, MeiliSearchResponse } from '../types' +import { SearchCacheInterface, MeiliSearchResponse } from '../types' import { stringifyArray } from '../utils' /** * @param {Record = {} -): ResponseCacher { +): SearchCacheInterface { const searchCache = cache return { getEntry: function (key: string) { diff --git a/src/client/index.ts b/src/client/index.ts index 0c6d0741..ab8d335c 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,3 +1 @@ export * from './instant-meilisearch-client' -export * from './search' -export * from './search-resolver' diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index e81f66ac..0d088984 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -7,9 +7,12 @@ import { InstantSearchParams, SearchContext, } from '../types' -import { adaptSearchResponse } from '../adapter' -import { SearchResolver } from './search-resolver' -import { ResponseCache } from '../cache' +import { + adaptSearchResponse, + adaptSearchParams, + SearchResolver, +} from '../adapter' +import { SearchCache } from '../cache/search-cache' /** * Create search context. @@ -64,7 +67,7 @@ export function instantMeiliSearch( apiKey: string, meiliSearchOptions: InstantMeiliSearchOptions = {} ): InstantMeiliSearchInstance { - const searchResolver = SearchResolver(ResponseCache()) + const searchResolver = SearchResolver(SearchCache()) return { MeiliSearchClient: new MeiliSearch({ host: hostUrl, apiKey: apiKey }), search: async function >( @@ -75,24 +78,32 @@ export function instantMeiliSearch( const searchRequest = instantSearchRequests[0] const { params: instantSearchParams } = searchRequest - const context = createContext( + const searchContext = createContext( searchRequest.indexName, instantSearchParams, meiliSearchOptions ) - const searchResponse = await searchResolver.searchResponse( - context, + // Adapt search request to MeiliSearch compliant search request + const adaptedSearchRequest = adaptSearchParams( instantSearchParams, + searchContext.paginationTotalHits, + searchContext.placeholderSearch, + searchContext.sort, + searchContext.query + ) + + const searchResponse = await searchResolver.searchResponse( + searchContext, + adaptedSearchRequest, this.MeiliSearchClient ) // Adapt the MeiliSearch responsne to a compliant instantsearch.js response const adaptedSearchResponse = adaptSearchResponse( - context.indexUid, searchResponse, instantSearchParams, - context + searchContext ) return adaptedSearchResponse } catch (e: any) { diff --git a/src/index.ts b/src/index.ts index e1048948..edfed4a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,2 @@ export * from './client' -export * from './adapter' export * from './types' diff --git a/src/types/types.ts b/src/types/types.ts index 119973bd..03a9a139 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -35,7 +35,7 @@ export type InstantMeiliSearchOptions = { primaryKey?: string } -export type ResponseCacher = { +export type SearchCacheInterface = { getEntry: (key: string) => MeiliSearchResponse | undefined formatKey: (components: any[]) => string setEntry: (searchResponse: MeiliSearchResponse, key: string) => void @@ -44,6 +44,7 @@ export type ResponseCacher = { export type SearchContext = { paginationTotalHits: number hitsPerPage: number + page: number primaryKey?: string placeholderSearch: boolean sort?: string From 5ec2991d0df8a5b89c707f90ab83b25a1583a5dd Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 28 Sep 2021 16:15:03 +0200 Subject: [PATCH 05/17] Fix linting --- src/adapter/search-request-adapter/filters.ts | 8 ++++++-- src/adapter/search-response-adapter/highlight-adapter.ts | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/adapter/search-request-adapter/filters.ts b/src/adapter/search-request-adapter/filters.ts index 74b641a6..d7903c21 100644 --- a/src/adapter/search-request-adapter/filters.ts +++ b/src/adapter/search-request-adapter/filters.ts @@ -1,4 +1,9 @@ -import { Filter, ParsedFilter, FacetsDistribution, FilterCache } from '../../types' +import { + Filter, + ParsedFilter, + FacetsDistribution, + FilterCache, +} from '../../types' import { removeUndefined } from '../../utils' /** @@ -54,7 +59,6 @@ export function cacheFilters(filters?: Filter): FilterCache { ) } - /** * Assign missing filters to facetsDistribution. * All facet passed as filter should appear in the facetsDistribution. diff --git a/src/adapter/search-response-adapter/highlight-adapter.ts b/src/adapter/search-response-adapter/highlight-adapter.ts index e0a30603..dcaab659 100644 --- a/src/adapter/search-response-adapter/highlight-adapter.ts +++ b/src/adapter/search-response-adapter/highlight-adapter.ts @@ -42,7 +42,7 @@ function adaptHighlight( // formattedHit is the `_formatted` object returned by MeiliSearch. // It contains all the highlighted and croped attributes return Object.keys(formattedHit).reduce((result, key) => { - ; (result[key] as any) = { + ;(result[key] as any) = { value: replaceHighlightTags( formattedHit[key], highlightPreTag, @@ -108,7 +108,7 @@ function adaptSnippet( // It contains all the highlighted and croped attributes return (Object.keys(formattedHit) as any[]).reduce((result, key) => { if (attributesToSnippet?.includes(key)) { - ; (result[key] as any) = { + ;(result[key] as any) = { value: snippetValue( formattedHit[key], snippetEllipsisText, From 57ad7d76a2ac800611162a71bfa0675f994843eb Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 28 Sep 2021 17:16:54 +0200 Subject: [PATCH 06/17] Add tests on search cache --- .../search-request-adapter/search-resolver.ts | 2 +- src/cache/__tests__/assets/utils.ts | 9 ++ src/cache/__tests__/search-cache.tests.ts | 103 ++++++++++++++++++ src/cache/search-cache.ts | 10 +- src/types/types.ts | 2 +- src/utils/string.ts | 2 +- 6 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 src/cache/__tests__/assets/utils.ts create mode 100644 src/cache/__tests__/search-cache.tests.ts diff --git a/src/adapter/search-request-adapter/search-resolver.ts b/src/adapter/search-request-adapter/search-resolver.ts index a8be8dd2..84b9eb7c 100644 --- a/src/adapter/search-request-adapter/search-resolver.ts +++ b/src/adapter/search-request-adapter/search-resolver.ts @@ -49,7 +49,7 @@ export function SearchResolver(cache: SearchCacheInterface) { ) // Cache response - cache.setEntry(searchResponse, key) + cache.setEntry(key, searchResponse) return searchResponse }, } diff --git a/src/cache/__tests__/assets/utils.ts b/src/cache/__tests__/assets/utils.ts new file mode 100644 index 00000000..8971dc84 --- /dev/null +++ b/src/cache/__tests__/assets/utils.ts @@ -0,0 +1,9 @@ +export const searchResponse = { + hits: [], + query: '', + offset: 0, + limit: 0, + processingTimeMs: 0, + nbHits: 0, + exhaustiveNbHits: false, +} diff --git a/src/cache/__tests__/search-cache.tests.ts b/src/cache/__tests__/search-cache.tests.ts new file mode 100644 index 00000000..1abff0c7 --- /dev/null +++ b/src/cache/__tests__/search-cache.tests.ts @@ -0,0 +1,103 @@ +import { SearchCache } from '../search-cache' +import { searchResponse } from './assets/utils' + +describe('Tests on entries in cache', () => { + test('Test to getEntry on empty cache', () => { + const cache = SearchCache() + const key = cache.getEntry('') + + expect(key).toBeUndefined() + }) + + test('Test to getEntry on invalid json', () => { + const cache = SearchCache({ myKey: 'myValue' }) + const key = cache.getEntry('myKey') + + expect(key).toEqual('myValue') + }) + + test('Test to getEntry on valid json string', () => { + const cache = SearchCache({ myKey: '"myValue"' }) + const key = cache.getEntry('myKey') + + expect(key).toEqual('myValue') + }) + + test('Test to getEntry on valid json object', () => { + const cache = SearchCache({ myKey: '{ "id": 1 }' }) + const key = cache.getEntry('myKey') + + expect(key).toHaveProperty('id', 1) + }) + + test('Test to getEntry on invalid json object', () => { + const cache = SearchCache({ myKey: '{ id: 1 }' }) + const key = cache.getEntry('myKey') + + expect(key).toEqual('{ id: 1 }') + }) +}) + +describe('Tests on key format', () => { + test('Test to format an empty string', () => { + const cache = SearchCache() + const key = cache.formatKey(['']) + expect(key).toEqual('""') + }) + + test('Test to format a number', () => { + const cache = SearchCache() + const key = cache.formatKey([1]) + expect(key).toEqual('1') + }) + + test('Test to format multiple empty strings', () => { + const cache = SearchCache() + const key = cache.formatKey(['', '', '']) + expect(key).toEqual('""""""') + }) + test('Test to format empty string', () => { + const cache = SearchCache() + const key = cache.formatKey([]) + expect(key).toEqual('') + }) + + test('Test to format undefined', () => { + const cache = SearchCache() + const key = cache.formatKey([undefined]) + expect(key).toEqual('undefined') + }) +}) + +describe('Tests on setEntry in cache', () => { + test('Set a response on a key', () => { + const cache = SearchCache() + const key = 'test' + const formattedKey = cache.formatKey([key]) + cache.setEntry(formattedKey, searchResponse) + const cached = cache.getEntry(formattedKey) + + expect(JSON.stringify(cached)).toEqual(JSON.stringify(searchResponse)) + }) + + test('Set a response on an empty key', () => { + const cache = SearchCache() + const key = '' + const formattedKey = cache.formatKey([key]) + cache.setEntry(formattedKey, searchResponse) + const cached = cache.getEntry(formattedKey) + + expect(JSON.stringify(cached)).toEqual(JSON.stringify(searchResponse)) + }) + + test('Set a response on an existing key', () => { + const cache = SearchCache() + const key = 'test' + const formattedKey = cache.formatKey([key]) + cache.setEntry(formattedKey, searchResponse) + cache.setEntry(formattedKey, searchResponse) + const cached = cache.getEntry(formattedKey) + + expect(JSON.stringify(cached)).toEqual(JSON.stringify(searchResponse)) + }) +}) diff --git a/src/cache/search-cache.ts b/src/cache/search-cache.ts index e2c81ab7..552e7dc0 100644 --- a/src/cache/search-cache.ts +++ b/src/cache/search-cache.ts @@ -10,13 +10,19 @@ export function SearchCache( const searchCache = cache return { getEntry: function (key: string) { - if (searchCache[key]) return JSON.parse(searchCache[key]) + if (searchCache[key]) { + try { + return JSON.parse(searchCache[key]) + } catch (_) { + return searchCache[key] + } + } return undefined }, formatKey: function (components: any[]) { return stringifyArray(components) }, - setEntry: function (searchResponse: MeiliSearchResponse, key: string) { + setEntry: function (key: string, searchResponse: MeiliSearchResponse) { searchCache[key] = JSON.stringify(searchResponse) }, } diff --git a/src/types/types.ts b/src/types/types.ts index 03a9a139..c45ebd63 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -38,7 +38,7 @@ export type InstantMeiliSearchOptions = { export type SearchCacheInterface = { getEntry: (key: string) => MeiliSearchResponse | undefined formatKey: (components: any[]) => string - setEntry: (searchResponse: MeiliSearchResponse, key: string) => void + setEntry: (key: string, searchResponse: MeiliSearchResponse) => void } export type SearchContext = { diff --git a/src/utils/string.ts b/src/utils/string.ts index cfe8cfe4..6e4503cb 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -21,6 +21,6 @@ export function replaceColonByEqualSign(filter: string): string { */ export function stringifyArray(arr: any[]): string { return arr.reduce((acc: string, curr: any) => { - return (acc += ` ${JSON.stringify(curr)}`) + return (acc += JSON.stringify(curr)) }, '') } From 44022d34579ed38240f18b4397bfc76f640b86f8 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 28 Sep 2021 19:15:21 +0200 Subject: [PATCH 07/17] Add tests on search resolver --- package.json | 6 +- .../search-request-adapter/search-resolver.ts | 2 +- src/cache/search-cache.ts | 4 +- src/client/instant-meilisearch-client.ts | 4 +- src/types/types.ts | 2 +- tests/search-resolver.tests.ts | 71 +++ yarn.lock | 520 +++++------------- 7 files changed, 231 insertions(+), 378 deletions(-) create mode 100644 tests/search-resolver.tests.ts diff --git a/package.json b/package.json index a4822c5c..935ca482 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "test:watch": "yarn test --watch", "test": "jest --runInBand --selectProjects dom --selectProjects node", "test:build": "yarn build && jest --runInBand --selectProjects build", - "test:e2e": "concurrently --kill-others -s first \"NODE_ENV=test yarn playground:angular\" \"cypress run --env playground=angular\"", + "test:e2e": "concurrently --kill-others -s first \"yarn playground:vue\" \"cypress run --env playground=vue\"", "test:e2e:all": "sh scripts/e2e.sh", - "test:e2e:watch": "concurrently --kill-others -s first \"NODE_ENV=test yarn playground:angular\" \"cypress open --env playground=angular\"", + "test:e2e:watch": "concurrently --kill-others -s first \"yarn playground:vue\" \"cypress open --env playground=vue\"", "test:all": "yarn test:e2e:all && yarn test && test:build", "cy:open": "cypress open", "playground:vue": "yarn --cwd ./playgrounds/vue && yarn --cwd ./playgrounds/vue serve", @@ -71,7 +71,7 @@ "babel-jest": "^27.2.2", "concurrently": "^6.2.1", "cssnano": "^4.1.10", - "cypress": "^7.3.0", + "cypress": "^8.5.0", "eslint": "^7.21.0", "eslint-config-prettier": "^8.1.0", "eslint-config-standard": "^16.0.0", diff --git a/src/adapter/search-request-adapter/search-resolver.ts b/src/adapter/search-request-adapter/search-resolver.ts index 84b9eb7c..f24e2c97 100644 --- a/src/adapter/search-request-adapter/search-resolver.ts +++ b/src/adapter/search-request-adapter/search-resolver.ts @@ -49,7 +49,7 @@ export function SearchResolver(cache: SearchCacheInterface) { ) // Cache response - cache.setEntry(key, searchResponse) + cache.setEntry(key, searchResponse) return searchResponse }, } diff --git a/src/cache/search-cache.ts b/src/cache/search-cache.ts index 552e7dc0..7f957b4a 100644 --- a/src/cache/search-cache.ts +++ b/src/cache/search-cache.ts @@ -1,4 +1,4 @@ -import { SearchCacheInterface, MeiliSearchResponse } from '../types' +import { SearchCacheInterface } from '../types' import { stringifyArray } from '../utils' /** * @param {Record(key: string, searchResponse: T) { searchCache[key] = JSON.stringify(searchResponse) }, } diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index 0d088984..91dc3277 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -64,10 +64,12 @@ function createContext( */ export function instantMeiliSearch( hostUrl: string, - apiKey: string, + apiKey = '', meiliSearchOptions: InstantMeiliSearchOptions = {} ): InstantMeiliSearchInstance { + // create search resolver with included cache const searchResolver = SearchResolver(SearchCache()) + return { MeiliSearchClient: new MeiliSearch({ host: hostUrl, apiKey: apiKey }), search: async function >( diff --git a/src/types/types.ts b/src/types/types.ts index c45ebd63..26b22849 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -38,7 +38,7 @@ export type InstantMeiliSearchOptions = { export type SearchCacheInterface = { getEntry: (key: string) => MeiliSearchResponse | undefined formatKey: (components: any[]) => string - setEntry: (key: string, searchResponse: MeiliSearchResponse) => void + setEntry: (key: string, searchResponse: T) => void } export type SearchContext = { diff --git a/tests/search-resolver.tests.ts b/tests/search-resolver.tests.ts new file mode 100644 index 00000000..fc73e3a6 --- /dev/null +++ b/tests/search-resolver.tests.ts @@ -0,0 +1,71 @@ +import { Movies } from './assets/utils' +import { instantMeiliSearch } from '../src' +import { MeiliSearch } from 'meilisearch' +import { mocked } from 'ts-jest/utils' + +jest.mock('meilisearch') + +export const searchResponse = { + hits: [], + query: '', + offset: 0, + limit: 0, + processingTimeMs: 0, + nbHits: 0, + exhaustiveNbHits: false, +} + +// Mocking of MeiliSearch package +const mockedMeilisearch = mocked(MeiliSearch, true) +const mockedSearch = jest.fn(() => searchResponse) +const mockedIndex = jest.fn(() => { + return { + search: mockedSearch, + } +}) + +mockedMeilisearch.mockReturnValue({ + // @ts-ignore + index: mockedIndex, +}) + +describe('Pagination browser test', () => { + test('Test 1 hitsPerPage', async () => { + const searchClient = instantMeiliSearch('http://localhost:7700') + await searchClient.search([ + { + indexName: 'movies', + params: { + query: '', + hitsPerPage: 1, + }, + }, + ]) + + await searchClient.search([ + { + indexName: 'movies', + params: { + query: '', + hitsPerPage: 1, + }, + }, + ]) + + await searchClient.search([ + { + indexName: 'movies', + params: { + query: '122', + hitsPerPage: 1, + }, + }, + ]) + + expect(mockedMeilisearch).toHaveBeenCalledWith({ + host: 'http://localhost:7700', + apiKey: '', + }) + expect(mockedSearch).toHaveBeenCalledTimes(1) + }) +}) diff --git a/yarn.lock b/yarn.lock index 269cc747..3eef1874 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1161,20 +1161,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cypress/listr-verbose-renderer@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" - integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo= - dependencies: - chalk "^1.1.3" - cli-cursor "^1.0.2" - date-fns "^1.27.2" - figures "^1.7.0" - -"@cypress/request@^2.88.5": - version "2.88.5" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.5.tgz#8d7ecd17b53a849cfd5ab06d5abe7d84976375d7" - integrity sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA== +"@cypress/request@^2.88.6": + version "2.88.6" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.6.tgz#a970dd675befc6bdf8a8921576c01f51cc5798e9" + integrity sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1189,13 +1179,12 @@ isstream "~0.1.2" json-stringify-safe "~5.0.1" mime-types "~2.1.19" - oauth-sign "~0.9.0" performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" tough-cookie "~2.5.0" tunnel-agent "^0.6.0" - uuid "^3.3.2" + uuid "^8.3.2" "@cypress/xvfb@^1.2.4": version "1.2.4" @@ -1506,13 +1495,6 @@ estree-walker "^2.0.1" picomatch "^2.2.2" -"@samverschueren/stream-to-observable@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301" - integrity sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ== - dependencies: - any-observable "^0.3.0" - "@sinonjs/commons@^1.7.0": version "1.8.2" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b" @@ -1716,6 +1698,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" + integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^4.32.0": version "4.32.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.32.0.tgz#46d2370ae9311092f2a6f7246d28357daf2d4e89" @@ -1908,6 +1897,14 @@ agent-base@6: dependencies: debug "4" +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -1965,11 +1962,6 @@ ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-escapes@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" @@ -1977,15 +1969,12 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: dependencies: type-fest "^0.11.0" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" ansi-regex@^5.0.0: version "5.0.0" @@ -1997,11 +1986,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -2021,11 +2005,6 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -any-observable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" - integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog== - anymatch@^3.0.3, anymatch@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" @@ -2409,17 +2388,6 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^1.0.0, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -2482,19 +2450,17 @@ classnames@^2.2.5: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== -cli-cursor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" - integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= - dependencies: - restore-cursor "^1.0.1" +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-cursor@^2.0.0, cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: - restore-cursor "^2.0.0" + restore-cursor "^3.1.0" cli-table3@~0.6.0: version "0.6.0" @@ -2506,13 +2472,13 @@ cli-table3@~0.6.0: optionalDependencies: colors "^1.1.2" -cli-truncate@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" - integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ= +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== dependencies: - slice-ansi "0.0.4" - string-width "^1.0.1" + slice-ansi "^3.0.0" + string-width "^4.2.0" cliui@^7.0.2: version "7.0.4" @@ -2537,11 +2503,6 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" @@ -2592,6 +2553,11 @@ colorette@^1.3.0: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== +colorette@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + colors@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -2639,16 +2605,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - concurrently@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.2.1.tgz#d880fc1d77559084732fa514092a3d5109a0d5bf" @@ -2689,7 +2645,7 @@ core-js-pure@^3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.8.3.tgz#10e9e3b2592ecaede4283e8f3ad7020811587c02" integrity sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA== -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -2866,13 +2822,12 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -cypress@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-7.3.0.tgz#17345b8d18681c120f033e7d8fd0f0271e9d0d51" - integrity sha512-aseRCH1tRVCrM6oEfja6fR/bo5l6e4SkHRRSATh27UeN4f/ANC8U7tGIulmrISJVy9xuOkOdbYKbUb2MNM+nrw== +cypress@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.5.0.tgz#5712ca170913f8344bf167301205c4217c1eb9bd" + integrity sha512-MMkXIS+Ro2KETn4gAlG3tIc/7FiljuuCZP0zpd9QsRG6MZSyZW/l1J3D4iQM6WHsVxuX4rFChn5jPFlC2tNSvQ== dependencies: - "@cypress/listr-verbose-renderer" "^0.4.1" - "@cypress/request" "^2.88.5" + "@cypress/request" "^2.88.6" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" "@types/sinonjs__fake-timers" "^6.0.2" @@ -2883,26 +2838,30 @@ cypress@^7.3.0: cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" + cli-cursor "^3.1.0" cli-table3 "~0.6.0" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" - debug "4.3.2" + debug "^4.3.2" + enquirer "^2.3.6" eventemitter2 "^6.4.3" execa "4.1.0" executable "^4.1.1" - extract-zip "^1.7.0" + extract-zip "2.0.1" + figures "^3.2.0" fs-extra "^9.1.0" getos "^3.2.1" is-ci "^3.0.0" is-installed-globally "~0.4.0" lazy-ass "^1.6.0" - listr "^0.14.3" + listr2 "^3.8.3" lodash "^4.17.21" log-symbols "^4.0.0" minimist "^1.2.5" ospath "^1.2.2" pretty-bytes "^5.6.0" + proxy-from-env "1.0.0" ramda "~0.27.1" request-progress "^3.0.0" supports-color "^8.1.1" @@ -2932,11 +2891,6 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^1.27.2: - version "1.30.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" - integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== - date-fns@^2.16.1: version "2.23.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9" @@ -2947,7 +2901,7 @@ dayjs@^1.10.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== -debug@4, debug@4.3.2, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -3092,11 +3046,6 @@ electron-to-chromium@^1.3.811: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.822.tgz#7036edc7f669b0aa79e9801dc5f56866c6ddc0b2" integrity sha512-k7jG5oYYHxF4jx6PcqwHX3JVME/OjzolqOZiIogi9xtsfsmTjTdie4x88OakYFPEa8euciTgCCzvVNwvmjHb1Q== -elegant-spinner@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" - integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= - emittery@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" @@ -3119,7 +3068,7 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enquirer@^2.3.5: +enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -3189,7 +3138,7 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -3577,11 +3526,6 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -exit-hook@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" - integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -3604,15 +3548,16 @@ extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extract-zip@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" - integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== dependencies: - concat-stream "^1.6.2" - debug "^2.6.9" - mkdirp "^0.5.4" + debug "^4.1.1" + get-stream "^5.1.0" yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" extsprintf@1.3.0: version "1.3.0" @@ -3677,18 +3622,10 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -figures@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" - integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= - dependencies: - escape-string-regexp "^1.0.5" - object-assign "^4.1.0" - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: escape-string-regexp "^1.0.5" @@ -3839,7 +3776,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^5.0.0: +get-stream@^5.0.0, get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== @@ -3945,13 +3882,6 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -4101,10 +4031,10 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indent-string@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" - integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== indexes-of@^1.0.1: version "1.0.1" @@ -4119,7 +4049,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@~2.0.3: +inherits@2: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4233,18 +4163,6 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -4290,13 +4208,6 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-observable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e" - integrity sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA== - dependencies: - symbol-observable "^1.1.0" - is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -4307,11 +4218,6 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-promise@^2.1.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" - integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== - is-reference@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" @@ -4331,11 +4237,6 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - is-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" @@ -4370,7 +4271,7 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -isarray@^1.0.0, isarray@~1.0.0: +isarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -5101,49 +5002,18 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -listr-silent-renderer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" - integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= - -listr-update-renderer@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2" - integrity sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA== - dependencies: - chalk "^1.1.3" - cli-truncate "^0.2.1" - elegant-spinner "^1.0.1" - figures "^1.7.0" - indent-string "^3.0.0" - log-symbols "^1.0.2" - log-update "^2.3.0" - strip-ansi "^3.0.1" - -listr-verbose-renderer@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db" - integrity sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw== - dependencies: - chalk "^2.4.1" - cli-cursor "^2.1.0" - date-fns "^1.27.2" - figures "^2.0.0" - -listr@^0.14.3: - version "0.14.3" - resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" - integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== - dependencies: - "@samverschueren/stream-to-observable" "^0.3.0" - is-observable "^1.1.0" - is-promise "^2.1.0" - is-stream "^1.1.0" - listr-silent-renderer "^1.1.1" - listr-update-renderer "^0.5.0" - listr-verbose-renderer "^0.5.0" - p-map "^2.0.0" - rxjs "^6.3.3" +listr2@^3.8.3: + version "3.12.2" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.12.2.tgz#2d55cc627111603ad4768a9e87c9c7bb9b49997e" + integrity sha512-64xC2CJ/As/xgVI3wbhlPWVPx0wfTqbUAkpb7bjDi0thSWMqrf07UFhrfsGoo8YSXmF049Rp9C0cjLC8rZxK9A== + dependencies: + cli-truncate "^2.1.0" + colorette "^1.4.0" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^6.6.7" + through "^2.3.8" + wrap-ansi "^7.0.0" load-json-file@^2.0.0: version "2.0.0" @@ -5200,13 +5070,6 @@ lodash@4.x, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, l resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" - integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg= - dependencies: - chalk "^1.0.0" - log-symbols@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -5215,14 +5078,15 @@ log-symbols@^4.0.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -log-update@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" - integrity sha1-iDKP19HOeTiykoN0bwsbwSayRwg= +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== dependencies: - ansi-escapes "^3.0.0" - cli-cursor "^2.0.0" - wrap-ansi "^3.0.1" + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" loose-envify@^1.4.0: version "1.4.0" @@ -5327,11 +5191,6 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.45.0" -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -5354,7 +5213,7 @@ mkdirp@0.3.0: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4= -mkdirp@^0.5.4, mkdirp@~0.5.1: +mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5442,21 +5301,11 @@ nth-check@^1.0.2: dependencies: boolbase "~1.0.0" -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -5528,18 +5377,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= - dependencies: - mimic-fn "^1.0.0" - onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" @@ -5609,10 +5446,12 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" p-try@^1.0.0: version "1.0.0" @@ -6086,11 +5925,6 @@ pretty-format@^27.0.0, pretty-format@^27.2.2: ansi-styles "^5.0.0" react-is "^17.0.1" -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -6113,6 +5947,11 @@ prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +proxy-from-env@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= + psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -6205,19 +6044,6 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^2.2.2: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readdirp@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" @@ -6345,20 +6171,12 @@ resolve@1.20.0, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12. is-core-module "^2.2.0" path-parse "^1.0.6" -restore-cursor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" - integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= - dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== dependencies: - onetime "^2.0.0" + onetime "^5.1.0" signal-exit "^3.0.2" reusify@^1.0.4: @@ -6447,7 +6265,7 @@ run-parallel@^1.1.9: resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw== -rxjs@^6.3.3, rxjs@^6.6.3: +rxjs@^6.6.3, rxjs@^6.6.7: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -6459,7 +6277,7 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -6587,10 +6405,14 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" - integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" slice-ansi@^4.0.0: version "4.0.0" @@ -6712,23 +6534,6 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" @@ -6767,27 +6572,6 @@ string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.3: call-bind "^1.0.0" define-properties "^1.1.3" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -6824,11 +6608,6 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -6884,11 +6663,6 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -6945,6 +6719,11 @@ throttleit@^1.0.0: resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw= +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" @@ -7099,6 +6878,11 @@ type-fest@^0.11.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -7116,11 +6900,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - typescript@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" @@ -7194,7 +6973,7 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -7209,10 +6988,10 @@ util.promisify@~1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.0" -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3: version "2.2.0" @@ -7347,13 +7126,14 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wrap-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" - integrity sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo= +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrap-ansi@^7.0.0: version "7.0.0" From f7c1fb014d69d4d062b69b8fd727372f2e4a3a39 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Wed, 29 Sep 2021 12:07:28 +0200 Subject: [PATCH 08/17] Fix linting --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 6b800f4a..828c75fc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -149,6 +149,7 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-unnecessary-type-assertion': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/member-delimiter-style': [ 'error', { From cb8a5c1947ea19dcf9189c365a4497b837cfb61e Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Wed, 29 Sep 2021 14:38:24 +0200 Subject: [PATCH 09/17] Create complete search context --- .../search-request-adapter/filter-adapter.ts | 24 ++-- .../search-params-adapter.ts | 35 +++-- .../search-request-adapter/search-resolver.ts | 4 +- .../highlight-adapter.ts | 22 ++-- .../search-response-adapter/hits-adapter.ts | 23 ++-- .../pagination-adapter.ts | 2 +- .../search-response-adapter.ts | 21 ++- src/client/instant-meilisearch-client.ts | 123 ++++++++++-------- src/types/types.ts | 32 ++--- tests/search-resolver.tests.ts | 112 ++++++++++++---- 10 files changed, 232 insertions(+), 166 deletions(-) diff --git a/src/adapter/search-request-adapter/filter-adapter.ts b/src/adapter/search-request-adapter/filter-adapter.ts index 5e2e5e5b..f88d8e85 100644 --- a/src/adapter/search-request-adapter/filter-adapter.ts +++ b/src/adapter/search-request-adapter/filter-adapter.ts @@ -1,4 +1,4 @@ -import type { Filter, AlgoliaSearchOptions } from '../../types' +import type { Filter, SearchContext } from '../../types' import { replaceColonByEqualSign } from '../../utils' /** @@ -6,12 +6,10 @@ import { replaceColonByEqualSign } from '../../utils' * Change sign from `:` to `=` in nested filter object. * example: [`genres:comedy`] becomes [`genres=comedy`] * - * @param {AlgoliaSearchOptions['facetFilters']} filters? - * @returns Filter + * @param {SearchContext['facetFilters']} filters? + * @returns {Filter} */ -function transformFilter( - filters?: AlgoliaSearchOptions['facetFilters'] -): Filter { +function transformFilter(filters?: SearchContext['facetFilters']): Filter { if (typeof filters === 'string') { return replaceColonByEqualSign(filters) } else if (Array.isArray(filters)) @@ -34,7 +32,7 @@ function transformFilter( * If filter is array, return without change. * * @param {Filter} filter - * @returns Array + * @returns {Array} */ function filterToArray(filter: Filter): Array { // Filter is a string @@ -51,7 +49,7 @@ function filterToArray(filter: Filter): Array { * @param {Filter} facetFilters * @param {Filter} numericFilters * @param {string} filters - * @returns Filter + * @returns {Filter} */ function mergeFilters( facetFilters: Filter, @@ -82,14 +80,14 @@ function mergeFilters( * combining and transforming all provided filters. * * @param {string|undefined} filters - * @param {AlgoliaSearchOptions['numericFilters']} numericFilters - * @param {AlgoliaSearchOptions['facetFilters']} facetFilters - * @returns Filter + * @param {SearchContext['numericFilters']} numericFilters + * @param {SearchContext['facetFilters']} facetFilters + * @returns {Filter} */ export function adaptFilters( filters: string | undefined, - numericFilters: AlgoliaSearchOptions['numericFilters'], - facetFilters: AlgoliaSearchOptions['facetFilters'] + numericFilters: SearchContext['numericFilters'], + facetFilters: SearchContext['facetFilters'] ): Filter { const transformedFilter = transformFilter(facetFilters || []) const transformedNumericFilter = transformFilter(numericFilters || []) diff --git a/src/adapter/search-request-adapter/search-params-adapter.ts b/src/adapter/search-request-adapter/search-params-adapter.ts index 800de22e..ddecec6d 100644 --- a/src/adapter/search-request-adapter/search-params-adapter.ts +++ b/src/adapter/search-request-adapter/search-params-adapter.ts @@ -1,4 +1,4 @@ -import type { InstantSearchParams, MeiliSearchParams } from '../../types' +import type { MeiliSearchParams, SearchContext } from '../../types' import { adaptFilters } from './filter-adapter' @@ -6,46 +6,38 @@ import { adaptFilters } from './filter-adapter' * Adapt search request from instantsearch.js * to search request compliant with MeiliSearch * - * @param {InstantSearchParams} instantSearchParams - * @param {number} paginationTotalHits - * @param {boolean} placeholderSearch - * @param {string} sort? - * @param {string} query? - * @returns MeiliSearchParams + * @param {SearchContext} searchContext + * @returns {MeiliSearchParams} */ export function adaptSearchParams( - instantSearchParams: InstantSearchParams, - paginationTotalHits: number, - placeholderSearch: boolean, - sort?: string, - query?: string + searchContext: SearchContext ): MeiliSearchParams { // Creates search params object compliant with MeiliSearch const meiliSearchParams: Record = {} // Facets - const facets = instantSearchParams?.facets + const facets = searchContext?.facets if (facets?.length) { meiliSearchParams.facetsDistribution = facets } // Attributes To Crop - const attributesToCrop = instantSearchParams?.attributesToSnippet + const attributesToCrop = searchContext?.attributesToSnippet if (attributesToCrop) { meiliSearchParams.attributesToCrop = attributesToCrop } // Attributes To Retrieve - const attributesToRetrieve = instantSearchParams?.attributesToRetrieve + const attributesToRetrieve = searchContext?.attributesToRetrieve if (attributesToRetrieve) { meiliSearchParams.attributesToRetrieve = attributesToRetrieve } // Filter const filter = adaptFilters( - instantSearchParams?.filters, - instantSearchParams?.numericFilters, - instantSearchParams?.facetFilters + searchContext?.filters, + searchContext?.numericFilters, + searchContext?.facetFilters ) if (filter.length) { meiliSearchParams.filter = filter @@ -57,16 +49,21 @@ export function adaptSearchParams( } // Attributes To Highlight - meiliSearchParams.attributesToHighlight = instantSearchParams?.attributesToHighlight || [ + meiliSearchParams.attributesToHighlight = searchContext?.attributesToHighlight || [ '*', ] + const placeholderSearch = meiliSearchParams.placeholderSearch + const query = meiliSearchParams.query + const paginationTotalHits = meiliSearchParams.paginationTotalHits + if ((!placeholderSearch && query === '') || paginationTotalHits === 0) { meiliSearchParams.limit = 0 } else { meiliSearchParams.limit = paginationTotalHits } + const sort = searchContext.sort // Sort if (sort?.length) { meiliSearchParams.sort = [sort] diff --git a/src/adapter/search-request-adapter/search-resolver.ts b/src/adapter/search-request-adapter/search-resolver.ts index f24e2c97..f38c98fe 100644 --- a/src/adapter/search-request-adapter/search-resolver.ts +++ b/src/adapter/search-request-adapter/search-resolver.ts @@ -14,9 +14,9 @@ export function SearchResolver(cache: SearchCacheInterface) { return { /** * @param {SearchContext} searchContext - * @param {InstantSearchParams} instantsearchParams + * @param {MeiliSearchParams} searchParams * @param {MeiliSearch} client - * @returns {Promise>>} + * @returns Promise */ searchResponse: async function ( searchContext: SearchContext, diff --git a/src/adapter/search-response-adapter/highlight-adapter.ts b/src/adapter/search-response-adapter/highlight-adapter.ts index dcaab659..3d1f7694 100644 --- a/src/adapter/search-response-adapter/highlight-adapter.ts +++ b/src/adapter/search-response-adapter/highlight-adapter.ts @@ -1,5 +1,5 @@ import { isString } from '../../utils' -import { InstantSearchParams } from '../../types' +import { SearchContext } from '../../types' /** * Replace `em` tags in highlighted MeiliSearch hits to @@ -8,7 +8,7 @@ import { InstantSearchParams } from '../../types' * @param {string} value * @param {string} highlightPreTag? * @param {string} highlightPostTag? - * @returns string + * @returns {string} */ function replaceHighlightTags( value: any, @@ -32,7 +32,7 @@ function replaceHighlightTags( * @param {Record, @@ -58,7 +58,7 @@ function adaptHighlight( * @param {string} snippetEllipsisText? * @param {string} highlightPreTag? * @param {string} highlightPostTag? - * @returns string + * @returns {string} */ function snippetValue( value: string, @@ -125,17 +125,17 @@ function adaptSnippet( * Adapt MeiliSearch formating to formating compliant with instantsearch.js. * * @param {Record, - instantSearchParams: InstantSearchParams + searchContext: SearchContext ): Record { - const attributesToSnippet = instantSearchParams?.attributesToSnippet - const snippetEllipsisText = instantSearchParams?.snippetEllipsisText - const highlightPreTag = instantSearchParams?.highlightPreTag - const highlightPostTag = instantSearchParams?.highlightPostTag + const attributesToSnippet = searchContext?.attributesToSnippet + const snippetEllipsisText = searchContext?.snippetEllipsisText + const highlightPreTag = searchContext?.highlightPreTag + const highlightPostTag = searchContext?.highlightPostTag if (!formattedHit || formattedHit.length) return {} return { diff --git a/src/adapter/search-response-adapter/hits-adapter.ts b/src/adapter/search-response-adapter/hits-adapter.ts index 9243d908..7ddc29d9 100644 --- a/src/adapter/search-response-adapter/hits-adapter.ts +++ b/src/adapter/search-response-adapter/hits-adapter.ts @@ -1,20 +1,21 @@ -import type { InstantSearchParams, SearchContext } from '../../types' +import type { PaginationContext, SearchContext } from '../../types' import { adaptPagination } from './pagination-adapter' import { adaptFormating } from './highlight-adapter' /** - * @param {Array>, - instantSearchParams: InstantSearchParams, - instantMeiliSearchContext: SearchContext + hits: Array>, + searchContext: SearchContext, + paginationContext: PaginationContext ): any { - const { hitsPerPage, primaryKey, page } = instantMeiliSearchContext - const paginatedHits = adaptPagination(meiliSearchHits, page, hitsPerPage) + const { primaryKey } = searchContext + const { hitsPerPage, page } = paginationContext + const paginatedHits = adaptPagination(hits, page, hitsPerPage) return paginatedHits.map((hit: any) => { // Creates Hit object compliant with InstantSearch @@ -22,7 +23,7 @@ export function adaptHits( const { _formatted: formattedHit, _matchesInfo, ...restOfHit } = hit return { ...restOfHit, - ...adaptFormating(formattedHit, instantSearchParams), + ...adaptFormating(formattedHit, searchContext), ...(primaryKey && { objectID: hit[primaryKey] }), } } diff --git a/src/adapter/search-response-adapter/pagination-adapter.ts b/src/adapter/search-response-adapter/pagination-adapter.ts index c7a53972..d5c378db 100644 --- a/src/adapter/search-response-adapter/pagination-adapter.ts +++ b/src/adapter/search-response-adapter/pagination-adapter.ts @@ -4,7 +4,7 @@ * @param {Record, diff --git a/src/adapter/search-response-adapter/search-response-adapter.ts b/src/adapter/search-response-adapter/search-response-adapter.ts index 105e7b83..9946e6b0 100644 --- a/src/adapter/search-response-adapter/search-response-adapter.ts +++ b/src/adapter/search-response-adapter/search-response-adapter.ts @@ -1,8 +1,8 @@ import type { SearchContext, - InstantSearchParams, MeiliSearchResponse, AlgoliaSearchResponse, + PaginationContext, } from '../../types' import { ceiledDivision } from '../../utils' import { adaptHits } from './hits-adapter' @@ -11,16 +11,15 @@ import { adaptHits } from './hits-adapter' * Adapt search response from MeiliSearch * to search response compliant with instantsearch.js * - * @param {string} indexUid * @param {MeiliSearchResponse> }} */ export function adaptSearchResponse( searchResponse: MeiliSearchResponse>, - instantSearchParams: InstantSearchParams, - searchContext: SearchContext + searchContext: SearchContext, + paginationContext: PaginationContext ): { results: Array> } { const searchResponseOptionals: Record = {} @@ -31,15 +30,11 @@ export function adaptSearchResponse( searchResponseOptionals.exhaustiveFacetsCount = exhaustiveFacetsCount } - const hits = adaptHits( - searchResponse.hits, - instantSearchParams, - searchContext - ) + const hits = adaptHits(searchResponse.hits, searchContext, paginationContext) const nbPages = ceiledDivision( searchResponse.hits.length, - searchContext.hitsPerPage + paginationContext.hitsPerPage ) const exhaustiveNbHits = searchResponse.exhaustiveNbHits @@ -47,7 +42,7 @@ export function adaptSearchResponse( const processingTimeMs = searchResponse.processingTimeMs const query = searchResponse.query - const { hitsPerPage, page } = searchContext + const { hitsPerPage, page } = paginationContext // Create response object compliant with InstantSearch const adaptedSearchResponse = { diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index 91dc3277..c904a718 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -5,7 +5,9 @@ import { AlgoliaSearchResponse, AlgoliaMultipleQueriesQuery, InstantSearchParams, + Context, SearchContext, + PaginationContext, } from '../types' import { adaptSearchResponse, @@ -14,46 +16,6 @@ import { } from '../adapter' import { SearchCache } from '../cache/search-cache' -/** - * Create search context. - * - * @param {string} indexName - * @param {InstantSearchParams} params - * @param {InstantMeiliSearchOptions={}} meiliSearchOptions - * @param {MeiliSearch} MeiliSearchClient - * @returns {SearchContext} - */ -function createContext( - indexName: string, - params: InstantSearchParams, - meiliSearchOptions: InstantMeiliSearchOptions = {} -): SearchContext { - const { - paginationTotalHits, - primaryKey, - placeholderSearch, - } = meiliSearchOptions - - const page = params?.page - const hitsPerPage = params?.hitsPerPage - - const query = params?.query - // Split index name and possible sorting rules - const [indexUid, ...sortByArray] = indexName.split(':') - - const context = { - indexUid: indexUid, - paginationTotalHits: paginationTotalHits || 200, - primaryKey: primaryKey || undefined, - placeholderSearch: placeholderSearch !== false, // true by default - hitsPerPage: hitsPerPage === undefined ? 20 : hitsPerPage, // 20 is the MeiliSearch's default limit value. `hitsPerPage` can be changed with `InsantSearch.configure`. - page: page || 0, // default page is 0 if none is provided - sort: sortByArray.join(':') || '', - query, - } - return context -} - /** * Instanciate SearchClient required by instantsearch.js. * @@ -65,36 +27,45 @@ function createContext( export function instantMeiliSearch( hostUrl: string, apiKey = '', - meiliSearchOptions: InstantMeiliSearchOptions = {} + options: InstantMeiliSearchOptions = {} ): InstantMeiliSearchInstance { // create search resolver with included cache const searchResolver = SearchResolver(SearchCache()) + const context: Context = { + primaryKey: options.primaryKey || undefined, + placeholderSearch: options.placeholderSearch !== false, // true by default + paginationTotalHits: options.paginationTotalHits || 200, + } + return { MeiliSearchClient: new MeiliSearch({ host: hostUrl, apiKey: apiKey }), + + /** + * @param {readonlyAlgoliaMultipleQueriesQuery[]} instantSearchRequests + * @returns {Array} + */ search: async function >( instantSearchRequests: readonly AlgoliaMultipleQueriesQuery[] - // options?: RequestOptions & MultipleQueriesOptions - When is this used ? ): Promise<{ results: Array> }> { try { const searchRequest = instantSearchRequests[0] const { params: instantSearchParams } = searchRequest - const searchContext = createContext( - searchRequest.indexName, - instantSearchParams, - meiliSearchOptions + const searchContext: SearchContext = createSearchContext( + searchRequest, + context ) + console.log(searchContext) - // Adapt search request to MeiliSearch compliant search request - const adaptedSearchRequest = adaptSearchParams( - instantSearchParams, - searchContext.paginationTotalHits, - searchContext.placeholderSearch, - searchContext.sort, - searchContext.query + const paginationContext = createPaginationContext( + searchContext, + instantSearchParams ) + // Adapt search request to MeiliSearch compliant search request + const adaptedSearchRequest = adaptSearchParams(searchContext) + const searchResponse = await searchResolver.searchResponse( searchContext, adaptedSearchRequest, @@ -104,8 +75,8 @@ export function instantMeiliSearch( // Adapt the MeiliSearch responsne to a compliant instantsearch.js response const adaptedSearchResponse = adaptSearchResponse( searchResponse, - instantSearchParams, - searchContext + searchContext, + paginationContext ) return adaptedSearchResponse } catch (e: any) { @@ -123,3 +94,45 @@ export function instantMeiliSearch( }, } } + +/** + * @param {AlgoliaMultipleQueriesQuery} searchRequest + * @param {Context} options + * @returns {SearchContext} + */ +function createSearchContext( + searchRequest: AlgoliaMultipleQueriesQuery, + options: Context +): SearchContext { + console.log(searchRequest) + + // Split index name and possible sorting rules + const [indexUid, ...sortByArray] = searchRequest.indexName.split(':') + const { params: instantSearchParams } = searchRequest + console.log({ indexUid, sortByArray }) + + const searchContext: SearchContext = { + ...options, + ...instantSearchParams, + sort: sortByArray.join(':') || '', + indexUid, + } + return searchContext +} + +/** + * @param {AlgoliaMultipleQueriesQuery} searchRequest + * @param {Context} options + * @returns {SearchContext} + */ +function createPaginationContext( + searchContext: SearchContext, + params: InstantSearchParams +): PaginationContext { + return { + paginationTotalHits: searchContext.paginationTotalHits || 200, + hitsPerPage: + searchContext.hitsPerPage === undefined ? 20 : searchContext.hitsPerPage, // 20 is the MeiliSearch's default limit value. `hitsPerPage` can be changed with `InsantSearch.configure`. + page: params?.page || 0, // default page is 0 if none is provided + } +} diff --git a/src/types/types.ts b/src/types/types.ts index 26b22849..a3109e78 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -3,13 +3,11 @@ import type { SearchResponse as MeiliSearchResponse, } from 'meilisearch' import type { SearchClient } from 'instantsearch.js' -import type { - SearchOptions as AlgoliaSearchOptions, - MultipleQueriesQuery as AlgoliaMultipleQueriesQuery, -} from '@algolia/client-search' +import type { MultipleQueriesQuery as AlgoliaMultipleQueriesQuery } from '@algolia/client-search' -export type { AlgoliaMultipleQueriesQuery, AlgoliaSearchOptions } +export type { AlgoliaMultipleQueriesQuery } export type { SearchResponse as AlgoliaSearchResponse } from '@algolia/client-search' + export type { Filter, FacetsDistribution, @@ -35,27 +33,31 @@ export type InstantMeiliSearchOptions = { primaryKey?: string } +export type Context = { + paginationTotalHits: number + placeholderSearch: boolean + primaryKey?: string +} + export type SearchCacheInterface = { getEntry: (key: string) => MeiliSearchResponse | undefined formatKey: (components: any[]) => string setEntry: (key: string, searchResponse: T) => void } -export type SearchContext = { - paginationTotalHits: number - hitsPerPage: number - page: number +export type SearchContext = InstantSearchParams & { primaryKey?: string - placeholderSearch: boolean + placeholderSearch?: boolean sort?: string - query?: string indexUid: string + paginationTotalHits: number } -export type AdaptToMeiliSearchParams = ( - instantSearchParams: InstantSearchParams, - instantMeiliSearchContext: SearchContext -) => Record +export type PaginationContext = { + paginationTotalHits: number + hitsPerPage: number + page: number +} export type InstantMeiliSearchInstance = SearchClient & { MeiliSearchClient: MeiliSearch diff --git a/tests/search-resolver.tests.ts b/tests/search-resolver.tests.ts index fc73e3a6..fc81a1ae 100644 --- a/tests/search-resolver.tests.ts +++ b/tests/search-resolver.tests.ts @@ -30,42 +30,102 @@ mockedMeilisearch.mockReturnValue({ }) describe('Pagination browser test', () => { - test('Test 1 hitsPerPage', async () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('Test two same search parameters twice', async () => { + const searchParameters = { + indexName: 'movies', + params: { + query: '', + }, + } + const searchClient = instantMeiliSearch('http://localhost:7700') + await searchClient.search([searchParameters]) + await searchClient.search([searchParameters]) + + expect(mockedMeilisearch).toHaveBeenCalledWith({ + host: 'http://localhost:7700', + apiKey: '', + }) + expect(mockedSearch).toHaveBeenCalledTimes(1) + }) + + test('Test two different search parameters', async () => { + const searchParameters1 = { + indexName: 'movies', + params: { + query: '', + }, + } + + const searchParameters2 = { + indexName: 'movies', + params: { + query: 'other query', + }, + } const searchClient = instantMeiliSearch('http://localhost:7700') - await searchClient.search([ - { - indexName: 'movies', - params: { - query: '', - hitsPerPage: 1, - }, + await searchClient.search([searchParameters1]) + await searchClient.search([searchParameters2]) + + expect(mockedMeilisearch).toHaveBeenCalledWith({ + host: 'http://localhost:7700', + apiKey: '', + }) + expect(mockedSearch).toHaveBeenCalledTimes(2) + }) + test('Test two same and one different search parameter', async () => { + const searchParameters1 = { + indexName: 'movies', + params: { + query: '', + }, + } + + const searchParameters2 = { + indexName: 'movies', + params: { + query: 'other query', }, - ]) + } + const searchClient = instantMeiliSearch('http://localhost:7700') + await searchClient.search([searchParameters1]) + await searchClient.search([searchParameters2]) + await searchClient.search([searchParameters1]) - await searchClient.search([ - { - indexName: 'movies', - params: { - query: '', - hitsPerPage: 1, - }, + expect(mockedMeilisearch).toHaveBeenCalledWith({ + host: 'http://localhost:7700', + apiKey: '', + }) + expect(mockedSearch).toHaveBeenCalledTimes(2) + }) + + test('Test two same and two different search parameter', async () => { + const searchParameters1 = { + indexName: 'movies', + params: { + query: '', }, - ]) + } - await searchClient.search([ - { - indexName: 'movies', - params: { - query: '122', - hitsPerPage: 1, - }, + const searchParameters2 = { + indexName: 'movies', + params: { + query: 'other query', }, - ]) + } + const searchClient = instantMeiliSearch('http://localhost:7700') + await searchClient.search([searchParameters1]) + await searchClient.search([searchParameters2]) + await searchClient.search([searchParameters1]) + await searchClient.search([searchParameters2]) expect(mockedMeilisearch).toHaveBeenCalledWith({ host: 'http://localhost:7700', apiKey: '', }) - expect(mockedSearch).toHaveBeenCalledTimes(1) + expect(mockedSearch).toHaveBeenCalledTimes(2) }) }) From 1c81573e6f0db66eac71b47a5fe2a10548914a9a Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Thu, 30 Sep 2021 18:24:14 +0200 Subject: [PATCH 10/17] Update src/client/instant-meilisearch-client.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amélie --- src/client/instant-meilisearch-client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index c904a718..d067cd48 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -56,7 +56,6 @@ export function instantMeiliSearch( searchRequest, context ) - console.log(searchContext) const paginationContext = createPaginationContext( searchContext, From c394d6fa19bc53da6b1bba7c3f468fa26929c62b Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 4 Oct 2021 18:48:02 +0200 Subject: [PATCH 11/17] Update tests/search-resolver.tests.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amélie --- tests/search-resolver.tests.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/search-resolver.tests.ts b/tests/search-resolver.tests.ts index fc81a1ae..667ce43b 100644 --- a/tests/search-resolver.tests.ts +++ b/tests/search-resolver.tests.ts @@ -75,8 +75,9 @@ describe('Pagination browser test', () => { apiKey: '', }) expect(mockedSearch).toHaveBeenCalledTimes(2) - }) - test('Test two same and one different search parameter', async () => { + }) + + test('Test two identical and one different search parameters', async () => { const searchParameters1 = { indexName: 'movies', params: { From 51e90816249c54f2c6e3e71b48df2ad1542c9d5c Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 4 Oct 2021 18:49:06 +0200 Subject: [PATCH 12/17] Update tests/search-resolver.tests.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amélie --- tests/search-resolver.tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/search-resolver.tests.ts b/tests/search-resolver.tests.ts index 667ce43b..fd343f35 100644 --- a/tests/search-resolver.tests.ts +++ b/tests/search-resolver.tests.ts @@ -34,7 +34,7 @@ describe('Pagination browser test', () => { jest.clearAllMocks() }) - test('Test two same search parameters twice', async () => { + test('Test the same search parameters twice', async () => { const searchParameters = { indexName: 'movies', params: { From 6857ba57f4469c54a370f9ca34b5b94dbe8e9d0c Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 4 Oct 2021 18:49:14 +0200 Subject: [PATCH 13/17] Update src/adapter/search-request-adapter/search-resolver.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amélie --- src/adapter/search-request-adapter/search-resolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapter/search-request-adapter/search-resolver.ts b/src/adapter/search-request-adapter/search-resolver.ts index f38c98fe..6ecf5c07 100644 --- a/src/adapter/search-request-adapter/search-resolver.ts +++ b/src/adapter/search-request-adapter/search-resolver.ts @@ -16,7 +16,7 @@ export function SearchResolver(cache: SearchCacheInterface) { * @param {SearchContext} searchContext * @param {MeiliSearchParams} searchParams * @param {MeiliSearch} client - * @returns Promise + * @returns {Promise} */ searchResponse: async function ( searchContext: SearchContext, From 63a8d64ce247a2962a05a3e17c0dba9489595f48 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 4 Oct 2021 18:49:22 +0200 Subject: [PATCH 14/17] Update src/client/instant-meilisearch-client.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amélie --- src/client/instant-meilisearch-client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index d067cd48..431862fe 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -108,7 +108,6 @@ function createSearchContext( // Split index name and possible sorting rules const [indexUid, ...sortByArray] = searchRequest.indexName.split(':') const { params: instantSearchParams } = searchRequest - console.log({ indexUid, sortByArray }) const searchContext: SearchContext = { ...options, From 88ea5e68c3b6efe9835ae928345bbbb6defa6c07 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 4 Oct 2021 18:49:28 +0200 Subject: [PATCH 15/17] Update src/client/instant-meilisearch-client.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amélie --- src/client/instant-meilisearch-client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index 431862fe..5d0fe4ae 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -103,7 +103,6 @@ function createSearchContext( searchRequest: AlgoliaMultipleQueriesQuery, options: Context ): SearchContext { - console.log(searchRequest) // Split index name and possible sorting rules const [indexUid, ...sortByArray] = searchRequest.indexName.split(':') From fb7c192d9b1085b5f686dad6622942fae5616768 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Mon, 4 Oct 2021 19:05:20 +0200 Subject: [PATCH 16/17] Improve filters implementation --- src/adapter/search-request-adapter/filters.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/adapter/search-request-adapter/filters.ts b/src/adapter/search-request-adapter/filters.ts index d7903c21..52b1aeb2 100644 --- a/src/adapter/search-request-adapter/filters.ts +++ b/src/adapter/search-request-adapter/filters.ts @@ -15,7 +15,7 @@ const adaptFilterSyntax = (filter: string) => { const [_, filterName, value] = matches return [{ filterName, value }] } - return [undefined] + return [] } /** @@ -35,7 +35,7 @@ function extractFilters(filters?: Filter): Array { }) .flat(2) } - return [undefined] + return [] } /** @@ -70,21 +70,23 @@ export function cacheFilters(filters?: Filter): FilterCache { * @returns {FacetsDistribution} */ export function assignMissingFilters( - cache?: FilterCache, + cachedFilters?: FilterCache, distribution?: FacetsDistribution ): FacetsDistribution { distribution = distribution || {} - if (cache && Object.keys(cache).length > 0) { - for (const cachedFacet in cache) { - for (const cachedField of cache[cachedFacet]) { - // if cached field is not present in the returned distribution - if ( - !distribution[cachedFacet] || - !Object.keys(distribution[cachedFacet]).includes(cachedField) - ) { + // If cachedFilters contains something + if (cachedFilters && Object.keys(cachedFilters).length > 0) { + // for all filters in cached filters + for (const cachedFacet in cachedFilters) { + // if facet does not exist on returned distribution, add an empty object + if (!distribution[cachedFacet]) distribution[cachedFacet] = {} + // for all fields in every filter + for (const cachedField of cachedFilters[cachedFacet]) { + // if the field is not present in the returned distribution + // set it at 0 + if (!Object.keys(distribution[cachedFacet]).includes(cachedField)) { // add 0 value - distribution[cachedFacet] = distribution[cachedFacet] || {} distribution[cachedFacet][cachedField] = 0 } } From 7593b7045d637c3b31a65c38d5e292e8d277a9a1 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Mon, 4 Oct 2021 19:09:13 +0200 Subject: [PATCH 17/17] Fix linting --- src/client/instant-meilisearch-client.ts | 1 - tests/search-resolver.tests.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index 5d0fe4ae..9284b8cd 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -103,7 +103,6 @@ function createSearchContext( searchRequest: AlgoliaMultipleQueriesQuery, options: Context ): SearchContext { - // Split index name and possible sorting rules const [indexUid, ...sortByArray] = searchRequest.indexName.split(':') const { params: instantSearchParams } = searchRequest diff --git a/tests/search-resolver.tests.ts b/tests/search-resolver.tests.ts index fd343f35..101ee00b 100644 --- a/tests/search-resolver.tests.ts +++ b/tests/search-resolver.tests.ts @@ -75,8 +75,8 @@ describe('Pagination browser test', () => { apiKey: '', }) expect(mockedSearch).toHaveBeenCalledTimes(2) - }) - + }) + test('Test two identical and one different search parameters', async () => { const searchParameters1 = { indexName: 'movies',