quicklit (0.0.1)
Installation
registry=npm install quicklit@0.0.1"quicklit": "0.0.1"About this package
Quicklit
A type-safe query parser, builder, and stringifier for Quickwit and Tantivy.
Quicklit allows you to parse raw Lucene-like query strings into a structured Abstract Syntax Tree (AST), programmatically build complex queries using a fluent API, and stringify them back into safe, escaped query strings ready for your search engine.
Features
- Type-Safe AST: Full TypeScript definitions for every node type (Term, Range, Boolean, etc.).
- Robust Parser: Handles complex nesting, operator precedence, and edge cases.
- Fluent Builder: Construct queries programmatically (
q.and(...)) without messy string concatenation. - Safe Stringification: Automatically escapes special characters to prevent syntax errors.
- Feature Complete: Supports:
- Boolean Logic (
AND,OR,NOT, Grouping()) - Ranges (
[10 TO 20],>,<=) - Sets (
IN [a, b, c]) - Phrase Queries & Slop (
"foo bar"~2) - Prefix (
term*) - Wildcards (
field:*)
- Boolean Logic (
Installation
npm install quicklit
# or
yarn add quicklit
# or
pnpm add quicklit
Usage
Parsing Query Strings
Turn a raw string input (e.g., from a search bar) into a structured AST. This validates the syntax and allows you to analyze or modify the query before sending it.
This is the complete round-trip from a raw user input, to a parsed Abstract syntax tree, over a modification of that AST to the rendering of the final search query.
import { parse } from 'quicklit';
const query = 'level:ERROR AND (duration:>1000 OR http_status:408)'
try {
const ast = parse(query);
console.log(JSON.stringify(ast, null, 2);
} catch (e) {
console.error("Invalid query syntax:", e.message);
}
Output AST:
{
"type": "And",
"children": [
{
"type": "Term",
"field": "level",
"value": "ERROR"
},
{
"type": "Group",
"child": {
"type": "Or",
"children": [
{
"type": "Range",
"field": "duration",
"lower": "1000",
"includeLower": false,
"includeUpper": false
},
{
"type": "Term",
"field": "http_status",
"value": "408"
}
]
}
}
]
}
Modify the query programmatically:
Use the q helper to build queries safely. This is perfect for constructing filters in your UI logic.
import { q } from "quicklit";
const modifiedAst = q.and(ast, q.prefix("service_name", "customer"));
Output modifiedAst:
{
"type": "And",
"children": [
{
"type": "And",
"children": [
{
"type": "Term",
"field": "level",
"value": "ERROR"
},
{
// ...
}
]
},
{
"type": "Prefix",
"value": "customer",
"field": "service_name"
}
]
}
Type Guards: Quicklit exports type guards to help you traverse or validate the AST at runtime.
import { isRangeNode, isTermNode, type QueryNode } from 'quicklit';
function analyzeNode(node: QueryNode) {
if (isRangeNode(node)) {
console.log(`Filtering range on field ${node.field} from ${node.from}`);
} else if (isTermNode(node)) {
console.log(`Searching for term: ${node.value}`);
}
}
Supported Syntax:
Quicklit supports the standard Quickwit/Tantivy syntax:
| Feature | Example Input | Builder Helper |
|---|---|---|
| Term | status:active |
q.term('active', 'status') |
| And | a AND b |
q.and(a, b) |
| Or | a OR b |
q.or(a, b) |
| Not | NOT lvl:WARN -lvl:WARN |
q.not(node) |
| Group | (a OR b) |
q.group(node) |
| Phrase | "hello world" |
q.phrase("hello world") |
| Slop | "foo bar"~2 |
q.phrase("foo bar", field, 2) |
| Range | val:[1 TO 5] |
q.range('val', 1, 5) |
| Comparison | val > 10 |
q.greaterThan('val', 10) |
| Set | id:IN [1, 2] |
q.in('id', ['1', '2']) |
| Wildcard | work* |
q.prefix('work') |
| Exists | author:* |
q.exists('author) |
Stringify an AST to a Quickwit Query:
import { stringify } from "quicklit";
const queryString = stringify(modifiedAst);
console.log(queryString);
Output of queryString:
level:ERROR AND (duration:{1000 TO *} OR http_status:408) AND customer:service*
Syntax Highlighting & UI Building
Quicklit exposes its internal Lexer and TokenType enum. This allows to build
rich search inputs with syntax highlighting, auto-completion, or validation
overlays without writing your own tokenizer.
The Lexer preserves whitespace and positions, making it perfect for mapping styles to text ranges.
import { tokenize, TokenType } from 'quicklit';
const query = 'status:active AND "hello world"';
const tokens = tokenize(query);
// Map tokens to CSS classes (e.g., Tailwind)
const html = tokens.map(token => {
let className = 'text-gray-800';
switch (token.type) {
case TokenType.Operator: // AND, OR
className = 'text-purple-600 font-bold';
break;
case TokenType.String: // "..."
className = 'text-green-600';
break;
case TokenType.Keyword: // IN, TO
className = 'text-blue-600 italic';
break;
case TokenType.Error:
className = 'text-red-500 underline decoration-wavy';
break;
// ...
}
return `<span class="${className}">${token.value}</span>`;
}).join('');
// Render `html` in your component...
Comparison: Why to use Quicklit?
| Feature | Template Strings | Quicklit |
|---|---|---|
| Syntax Validation | ❌ (Server errors) | ✅ (Fails fast locally) |
| Escaping | ❌ (Vulnerable) | ✅ (Automatic) |
| Composability | ❌ (Hard to merge) | ✅ (Easy tree merging) |
| Syntax Highlighting | ❌ | ✅ (Via Lexer API) |
| Type Safety | ❌ | ✅ |
Known issues/open tasks
- [] Add sample query editor implementation for documentation purpose
Development / Contributing
- Install dependencies:
npm install
- Run the unit tests (uses Vitest):
npm run test
- Build the library (uses tsdown):
npm run build
License
Dependencies
Development dependencies
| ID | Version |
|---|---|
| @types/node | ^25.3.0 |
| bumpp | ^10.4.1 |
| tsdown | ^0.20.3 |
| typescript | ^5.9.3 |
| vitest | ^4.0.18 |