Skip to content

Commit 8d9c32a

Browse files
8byr0Strift
andauthored
feature: handle per index meilisearch params (#1364)
* feat: handle per index meilisearch params * fix: typescript error * test: make sure adding an index properly overrides base meilisearchParams * test: override client meilisearchParams in context * lint: remove unused import * docs: update README with example for indexOverrides * feat: make facets and filters overridable * override filters even when instantsearch filters are empty * allow overriding sorting * lint * Merge existing override params when adding new with setMeilisearchParams --------- Co-authored-by: Strift <lau.cazanove@gmail.com>
1 parent 83040e5 commit 8d9c32a

6 files changed

Lines changed: 302 additions & 21 deletions

File tree

packages/instant-meilisearch/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,28 @@ instantMeiliSearch(
296296
}
297297
)
298298
```
299+
When using multi search, meilisearchParams can be overriden for specific indexes :
299300

301+
```js
302+
instantMeiliSearch(
303+
// ...
304+
{
305+
meiliSearchParams: {
306+
// All indexes will highlight overview
307+
attributesToHighlight: ['overview'],
308+
highlightPreTag: '<em>',
309+
highlightPostTag: '</em>',
310+
attributesToSearchOn: ['overview'],
311+
indexesOverrides: {
312+
movies: {
313+
// Only title will be highlighted for hits in movies
314+
attributesToHighlight: ['title']
315+
}
316+
}
317+
},
318+
}
319+
)
320+
```
300321
### Modify Meilisearch search parameters
301322

302323
`instantMeiliSearch` returns an instance with two properties on it, one of them being `setMeiliSearchParams`.

packages/instant-meilisearch/__tests__/overridden-meilisearch-parameters.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,71 @@ describe('InstantMeiliSearch overridden parameters', () => {
4747
expect(secondHits[0]._highlightResult?.overview?.value).toContain(
4848
'<om>While racing to a boxing match</om>'
4949
)
50+
51+
setMeiliSearchParams({
52+
indexesOverrides: {
53+
movies: { highlightPreTag: '<span>', highlightPostTag: '</span>' },
54+
},
55+
})
56+
const thirdResponse = await searchClient.search<Movies>(queryParams)
57+
58+
const thirdHits = thirdResponse.results[0].hits
59+
expect(thirdHits.length).toEqual(1)
60+
expect(thirdHits[0]._highlightResult?.overview?.value).toContain(
61+
'<span>While racing to a boxing match</span>'
62+
)
63+
})
64+
65+
test('sort parameter precedence: per-index overrides global overrides', async () => {
66+
await meilisearchClient
67+
.index('movies')
68+
.updateSettings({
69+
sortableAttributes: ['release_date', 'title'],
70+
})
71+
.waitTask()
72+
73+
const { searchClient, setMeiliSearchParams } = instantMeiliSearch(
74+
'http://localhost:7700',
75+
'masterKey',
76+
{
77+
meiliSearchParams: {
78+
sort: ['title:asc'],
79+
},
80+
}
81+
)
82+
83+
const queryParams = [
84+
{
85+
indexName: 'movies',
86+
params: { query: '', hitsPerPage: 3 },
87+
},
88+
]
89+
90+
// Test global sort override - should sort by title ascending
91+
const globalResponse = await searchClient.search<Movies>(queryParams)
92+
const globalHits = globalResponse.results[0].hits
93+
expect(globalHits.length).toBeGreaterThan(1)
94+
// Verify titles are sorted in ascending order
95+
const globalTitles = globalHits
96+
.map((hit: Movies) => hit.title)
97+
.filter(Boolean)
98+
expect(globalTitles).toEqual([...globalTitles].sort())
99+
100+
// Test per-index sort override takes precedence over global
101+
setMeiliSearchParams({
102+
sort: ['title:asc'],
103+
indexesOverrides: {
104+
movies: { sort: ['title:desc'] },
105+
},
106+
})
107+
108+
const perIndexResponse = await searchClient.search<Movies>(queryParams)
109+
const perIndexHits = perIndexResponse.results[0].hits
110+
expect(perIndexHits.length).toBeGreaterThan(1)
111+
// Verify titles are sorted in descending order
112+
const perIndexTitles = perIndexHits
113+
.map((hit: Movies) => hit.title)
114+
.filter(Boolean)
115+
expect(perIndexTitles).toEqual([...perIndexTitles].sort().reverse())
50116
})
51117
})

packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/search-params.test.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,41 @@ describe('Parameters adapter', () => {
8080
)
8181
})
8282

83+
test('adapting a searchContext with overridden Meilisearch parameters for a specific index', () => {
84+
const meiliSearchParams: OverridableMeiliSearchSearchParameters = {
85+
attributesToHighlight: ['movies', 'genres'],
86+
highlightPreTag: '<em>',
87+
highlightPostTag: '</em>',
88+
matchingStrategy: MatchingStrategies.ALL,
89+
indexesOverrides: {
90+
test: {
91+
attributesToHighlight: ['release_date'],
92+
highlightPreTag: '<span>',
93+
highlightPostTag: '</span>',
94+
matchingStrategy: MatchingStrategies.LAST,
95+
},
96+
},
97+
}
98+
99+
const searchParams = adaptSearchParams({
100+
...DEFAULT_CONTEXT,
101+
meiliSearchParams,
102+
})
103+
104+
expect(searchParams.attributesToHighlight).toEqual(
105+
meiliSearchParams.indexesOverrides?.test?.attributesToHighlight
106+
)
107+
expect(searchParams.highlightPreTag).toEqual(
108+
meiliSearchParams.indexesOverrides?.test?.highlightPreTag
109+
)
110+
expect(searchParams.highlightPostTag).toEqual(
111+
meiliSearchParams.indexesOverrides?.test?.highlightPostTag
112+
)
113+
expect(searchParams.matchingStrategy).toEqual(
114+
meiliSearchParams.indexesOverrides?.test?.matchingStrategy
115+
)
116+
})
117+
83118
test('hybrid search configuration can be set via search parameters', () => {
84119
const hybridSearchConfig = {
85120
semanticRatio: 0,
@@ -96,6 +131,29 @@ describe('Parameters adapter', () => {
96131
expect(searchParams.hybrid).toBe(hybridSearchConfig)
97132
})
98133

134+
test('hybrid search configuration can be set via search parameters for a specific index', () => {
135+
const hybridSearchConfig = {
136+
semanticRatio: 0,
137+
embedder: 'default',
138+
}
139+
const specificHybridSearchConfig = {
140+
semanticRatio: 10,
141+
embedder: 'default',
142+
}
143+
144+
const searchParams = adaptSearchParams({
145+
...DEFAULT_CONTEXT,
146+
meiliSearchParams: {
147+
hybrid: hybridSearchConfig,
148+
indexesOverrides: {
149+
test: { hybrid: specificHybridSearchConfig },
150+
},
151+
},
152+
})
153+
154+
expect(searchParams.hybrid).toBe(specificHybridSearchConfig)
155+
})
156+
99157
test('vector can be set via search parameters', () => {
100158
const vector = [0, 1, 2]
101159

@@ -108,6 +166,20 @@ describe('Parameters adapter', () => {
108166

109167
expect(searchParams.vector).toBe(vector)
110168
})
169+
test('vector can be set via search parameters for a specific index', () => {
170+
const vector = [0, 1, 2]
171+
const indexVector = [3, 4, 5]
172+
173+
const searchParams = adaptSearchParams({
174+
...DEFAULT_CONTEXT,
175+
meiliSearchParams: {
176+
vector,
177+
indexesOverrides: { test: { vector: indexVector } },
178+
},
179+
})
180+
181+
expect(searchParams.vector).toBe(indexVector)
182+
})
111183

112184
test('ranking score threshold can be set via search parameters', () => {
113185
const rankingScoreThreshold = 0.974
@@ -122,6 +194,23 @@ describe('Parameters adapter', () => {
122194
expect(searchParams.rankingScoreThreshold).toBe(rankingScoreThreshold)
123195
})
124196

197+
test('ranking score threshold can be set via search parameters for a specific index', () => {
198+
const rankingScoreThreshold = 0.974
199+
const indexRankingScoreThreshold = 0.567
200+
201+
const searchParams = adaptSearchParams({
202+
...DEFAULT_CONTEXT,
203+
meiliSearchParams: {
204+
rankingScoreThreshold,
205+
indexesOverrides: {
206+
test: { rankingScoreThreshold: indexRankingScoreThreshold },
207+
},
208+
},
209+
})
210+
211+
expect(searchParams.rankingScoreThreshold).toBe(indexRankingScoreThreshold)
212+
})
213+
125214
test('distinct search configuration can be set via search parameters', () => {
126215
const distinctSearchConfig = 'title'
127216

@@ -134,6 +223,56 @@ describe('Parameters adapter', () => {
134223

135224
expect(searchParams.distinct).toBe(distinctSearchConfig)
136225
})
226+
227+
test('distinct search configuration can be set via search parameters for a specific', () => {
228+
const distinctSearchConfig = 'title'
229+
const indexDistinctSearchConfig = 'name'
230+
231+
const searchParams = adaptSearchParams({
232+
...DEFAULT_CONTEXT,
233+
meiliSearchParams: {
234+
distinct: distinctSearchConfig,
235+
indexesOverrides: {
236+
test: { distinct: indexDistinctSearchConfig },
237+
},
238+
},
239+
})
240+
241+
expect(searchParams.distinct).toBe(indexDistinctSearchConfig)
242+
})
243+
244+
test('filter can be set via global override when IS filters are empty', () => {
245+
const globalFilter = 'category = "books"'
246+
247+
const searchParams = adaptSearchParams({
248+
...DEFAULT_CONTEXT,
249+
// No facetFilters, filters, or numericFilters - empty IS filters
250+
meiliSearchParams: {
251+
filter: globalFilter,
252+
},
253+
})
254+
255+
expect(searchParams.filter).toBe(globalFilter)
256+
})
257+
258+
test('filter can be set via per-index override when IS filters are empty', () => {
259+
const globalFilter = 'category = "books"'
260+
const indexFilter = 'status = "active"'
261+
262+
const searchParams = adaptSearchParams({
263+
...DEFAULT_CONTEXT,
264+
// No facetFilters, filters, or numericFilters - empty IS filters
265+
meiliSearchParams: {
266+
filter: globalFilter,
267+
indexesOverrides: {
268+
test: { filter: indexFilter },
269+
},
270+
},
271+
})
272+
273+
// Per-index override should take precedence over global override
274+
expect(searchParams.filter).toBe(indexFilter)
275+
})
137276
})
138277

139278
describe('Geo filter adapter', () => {

0 commit comments

Comments
 (0)