Analyseur, sérialiseur et moteur de recherche légers et performants de type Lucene.
Liqe initialement construit pour activer le filtrage des journaux Roarr via cli. Depuis, j’ai peaufiné ce projet comme passe-temps/exercice intellectuel. Je l'ai vu être adopté par diverses applications CLI et Web nécessitant une recherche avancée. À ma connaissance, il s'agit actuellement de l'analyseur et sérialiseur de syntaxe de type Lucene le plus complet en JavaScript, ainsi que d'un moteur de recherche en mémoire compatible.
Les cas d'utilisation de Liqe incluent :
Notez que Liqe AST est traité comme une API publique, c'est-à-dire que l'on peut implémenter son propre mécanisme de recherche qui utilise le langage de requête Liqe (LQL).
import {
filter ,
highlight ,
parse ,
test ,
} from 'liqe' ;
const persons = [
{
height : 180 ,
name : 'John Morton' ,
} ,
{
height : 175 ,
name : 'David Barker' ,
} ,
{
height : 170 ,
name : 'Thomas Castro' ,
} ,
] ;
Filtrer une collection :
filter ( parse ( 'height:>170' ) , persons ) ;
// [
// {
// height: 180,
// name: 'John Morton',
// },
// {
// height: 175,
// name: 'David Barker',
// },
// ]
Testez un seul objet :
test ( parse ( 'name:John' ) , persons [ 0 ] ) ;
// true
test ( parse ( 'name:David' ) , persons [ 0 ] ) ;
// false
Mettez en surbrillance les champs et sous-chaînes correspondants :
highlight ( parse ( 'name:john' ) , persons [ 0 ] ) ;
// [
// {
// path: 'name',
// query: /(John)/,
// }
// ]
highlight ( parse ( 'height:180' ) , persons [ 0 ] ) ;
// [
// {
// path: 'height',
// }
// ]
Liqe utilise Liqe Query Language (LQL), qui s'inspire fortement de Lucene mais l'étend de diverses manières qui permettent une expérience de recherche plus puissante.
# search for "foo" term anywhere in the document (case insensitive)
foo
# search for "foo" term anywhere in the document (case sensitive)
'foo'
"foo"
# search for "foo" term in `name` field
name :foo
# search for "foo" term in `full name` field
'full name' : foo
"full name" : foo
# search for "foo" term in `first` field, member of `name`, i.e.
# matches {name: {first: 'foo'}}
name . first :foo
# search using regex
name :/foo /
name :/ foo / o
# search using wildcard
name :foo * bar
name :foo? bar
# boolean search
member :true
member :false
# null search
member :null
# search for age =, >, >=, <, <=
height : = 100
height :>100
height :>=100
height :<100
height :<=100
# search for height in range (inclusive, exclusive)
height : [ 100 TO 200 ]
height : { 100 TO 200 }
# boolean operators
name :foo AND height : = 100
name :foo OR name : bar
# unary operators
NOT foo
- foo
NOT foo : bar
- foo :bar
name :foo AND NOT ( bio :bar OR bio : baz )
# implicit AND boolean operator
name :foo height : = 100
# grouping
name :foo AND ( bio :bar OR bio : baz )
Recherchez le mot "foo" dans n'importe quel champ (insensible à la casse).
foo
Recherchez le mot « foo » dans le champ name
.
name :foo
Recherchez les valeurs du champ name
correspondant à /foo/i
regex.
name :/foo / i
Recherchez les valeurs du champ name
correspondant au modèle de caractère générique f*o
.
name :f * o
Recherchez les valeurs du champ name
correspondant au modèle de caractère générique f?o
.
name :f? o
Recherchez l'expression « foo bar » dans le champ name
(sensible à la casse).
name :"foo bar"
Recherchez une valeur égale à 100 dans le champ height
.
height : = 100
Recherchez une valeur supérieure à 100 dans le champ height
.
height :>100
Recherchez une valeur supérieure ou égale à 100 dans le champ height
.
height :>=100
Recherchez une valeur supérieure ou égale à 100 et inférieure ou égale à 200 dans le champ height
.
height : [ 100 TO 200 ]
Recherchez une valeur supérieure à 100 et inférieure à 200 dans le champ height
.
height : { 100 TO 200 }
Recherchez n’importe quel mot commençant par « foo » dans le champ name
.
name :foo *
Recherchez n’importe quel mot commençant par « foo » et se terminant par « bar » dans le champ name
.
name :foo * bar
Recherchez n'importe quel mot commençant par « foo » dans le champ name
, suivi d'un seul caractère arbitraire.
name :foo?
Recherchez n'importe quel mot commençant par « foo », suivi d'un seul caractère arbitraire et se terminant immédiatement par « bar » dans le champ name
.
name :foo? bar
Recherchez l’expression « foo bar » dans le champ name
ET l’expression « quick fox » dans le champ bio
.
name :"foo bar" AND bio : "quick fox"
Recherchez soit l'expression « foo bar » dans le champ name
ET l'expression « quick fox » dans le champ bio
, soit le mot « fox » dans le champ name
.
( name :"foo bar" AND bio : "quick fox" ) OR name : fox
Serializer permet de reconvertir les jetons Liqe en requête de recherche d'origine.
import {
parse ,
serialize ,
} from 'liqe' ;
const tokens = parse ( 'foo:bar' ) ;
// {
// expression: {
// location: {
// start: 4,
// },
// quoted: false,
// type: 'LiteralExpression',
// value: 'bar',
// },
// field: {
// location: {
// start: 0,
// },
// name: 'foo',
// path: ['foo'],
// quoted: false,
// type: 'Field',
// },
// location: {
// start: 0,
// },
// operator: {
// location: {
// start: 3,
// },
// operator: ':',
// type: 'ComparisonOperator',
// },
// type: 'Tag',
// }
serialize ( tokens ) ;
// 'foo:bar'
import {
type BooleanOperatorToken ,
type ComparisonOperatorToken ,
type EmptyExpression ,
type FieldToken ,
type ImplicitBooleanOperatorToken ,
type ImplicitFieldToken ,
type LiteralExpressionToken ,
type LogicalExpressionToken ,
type RangeExpressionToken ,
type RegexExpressionToken ,
type TagToken ,
type UnaryOperatorToken ,
} from 'liqe' ;
Il existe 11 jetons AST qui décrivent une requête Liqe analysée.
Si vous créez un sérialiseur, vous devez tous les implémenter pour une couverture complète de toutes les entrées de requête possibles. Reportez-vous au sérialiseur intégré pour un exemple.
import {
isSafeUnquotedExpression ,
} from 'liqe' ;
/**
* Determines if an expression requires quotes.
* Use this if you need to programmatically manipulate the AST
* before using a serializer to convert the query back to text.
*/
isSafeUnquotedExpression ( expression : string ) : boolean ;
Les capacités Lucene suivantes ne sont pas prises en charge :
En cas d'erreur de syntaxe, Liqe renvoie SyntaxError
.
import {
parse ,
SyntaxError ,
} from 'liqe' ;
try {
parse ( 'foo bar' ) ;
} catch ( error ) {
if ( error instanceof SyntaxError ) {
console . error ( {
// Syntax error at line 1 column 5
message : error . message ,
// 4
offset : error . offset ,
// 1
offset : error . line ,
// 5
offset : error . column ,
} ) ;
} else {
throw error ;
}
}
Pensez à utiliser le package highlight-words
pour mettre en évidence les correspondances Liqe.
Si vous souhaitez modifier l'analyseur, utilisez npm run watch
pour exécuter le compilateur en mode surveillance.
Avant d'apporter des modifications, capturez le benchmark actuel sur votre machine à l'aide de npm run benchmark
. Exécutez à nouveau le benchmark après avoir apporté des modifications. Avant de valider des modifications, assurez-vous que les performances ne sont pas affectées négativement.