|
1 |
| -import { entries, Nil } from '@seedcompany/common'; |
| 1 | +import { cleanSplit, entries, Nil } from '@seedcompany/common'; |
2 | 2 | import {
|
3 | 3 | comparisions,
|
| 4 | + greaterThan, |
4 | 5 | inArray,
|
5 | 6 | isNull,
|
6 | 7 | node,
|
@@ -238,27 +239,55 @@ export const sub =
|
238 | 239 | export const fullText =
|
239 | 240 | ({
|
240 | 241 | index,
|
| 242 | + escapeLucene = true, |
| 243 | + toLucene, |
| 244 | + minScore = 0, |
241 | 245 | matchToNode,
|
242 | 246 | }: {
|
243 | 247 | index: () => FullTextIndex;
|
| 248 | + escapeLucene?: boolean; |
| 249 | + toLucene?: (input: string) => string; |
| 250 | + minScore?: number; |
244 | 251 | matchToNode: (query: Query) => Query;
|
245 | 252 | }) =>
|
246 | 253 | <T, K extends ConditionalKeys<T, string | undefined>>({
|
247 |
| - value: input, |
| 254 | + value, |
248 | 255 | key: field,
|
249 | 256 | query,
|
250 | 257 | }: BuilderArgs<T, K>) => {
|
251 |
| - if (!input || typeof input !== 'string') { |
| 258 | + if (!value || typeof value !== 'string') { |
252 | 259 | return null;
|
253 | 260 | }
|
254 |
| - const escaped = escapeLuceneSyntax(input); |
255 | 261 |
|
256 |
| - const lucene = `*${escaped}*`; |
| 262 | + let input: string = value; |
| 263 | + |
| 264 | + input = escapeLucene ? escapeLuceneSyntax(input) : input; |
| 265 | + |
| 266 | + const lucene = |
| 267 | + toLucene?.(input) ?? |
| 268 | + // Default to each word being matched. |
| 269 | + // And for each word... |
| 270 | + cleanSplit(input, ' ') |
| 271 | + .map((term) => { |
| 272 | + const adjusted = [ |
| 273 | + // fuzzy (distance) search with boosted priority |
| 274 | + `${term}~^2`, |
| 275 | + // word prefixes in case the distance is too great |
| 276 | + `*${term}*`, |
| 277 | + ].join(' OR '); |
| 278 | + return `(${adjusted})`; |
| 279 | + }) |
| 280 | + .join(' AND '); |
257 | 281 |
|
258 | 282 | query
|
259 | 283 | .subQuery((q) =>
|
260 | 284 | q
|
261 |
| - .call(index().search(lucene, { limit: 100 }).yield({ node: 'match' })) |
| 285 | + .call( |
| 286 | + index() |
| 287 | + .search(lucene, { limit: 100 }) |
| 288 | + .yield({ node: 'match', score: true }), |
| 289 | + ) |
| 290 | + .where({ score: greaterThan(minScore) }) |
262 | 291 | .apply(matchToNode)
|
263 | 292 | .return(collect('distinct node').as(`${field}Matches`)),
|
264 | 293 | )
|
|
0 commit comments