@filtron/core
Filtron parses human-friendly filter strings into structured queries you can use anywhere — SQL databases, in-memory arrays, or your own custom backend.
function parse(query: string, options?: ParseOptions): ParseResult;
Parses a Filtron query string into an Abstract Syntax Tree (AST).
| Parameter | Description |
|---|
query | The Filtron query string to parse |
options | Optional parse limits, see ParseOptions |
Returns A ParseResult containing either the AST or an error
const result = parse('age > 18 AND status = "active"');
if (result.success) {
console.log(result.ast);
} else {
console.error(result.message);
}
function parseOrThrow(query: string, options?: ParseOptions): ASTNode;
Parses a Filtron query string and throws an error if parsing fails. Use this when you want to handle errors with try/catch instead of checking the result.
| Parameter | Description |
|---|
query | The Filtron query string to parse |
options | Optional parse limits, see ParseOptions |
Returns The parsed AST
Throws FiltronParseError if parsing fails, with position information
try {
const ast = parseOrThrow('age > 18');
console.log(ast);
} catch (error) {
if (error instanceof FiltronParseError) {
console.error('Parse failed at position', error.position, ':', error.message);
}
}
class FiltronParseError extends Error {
constructor(message: string, position?: number);
position: number | undefined;
}
Error thrown when parsing a Filtron query fails. Includes the position in the query where the error occurred.
interface ParseOptions {
maxLength?: number;
maxDepth?: number;
}
Limits applied while parsing a query. Both values are upper bounds; pass larger numbers to raise them.
type ParseResult = ParseSuccess | ParseFailure;
The generic parse result
interface ParseSuccess {
success: true;
ast: ASTNode;
}
Result of a successful parse operation
interface ParseFailure {
success: false;
message: string;
position?: number;
}
Result of a failed parse operation
function walk(node: ASTNode, visitor: (node: ASTNode) => boolean | undefined): void;
Walks an AST in pre-order depth-first order, calling the visitor on each node
The visitor is called on a node before its children. Returning false from the visitor skips that node's children; any other return value (including undefined) descends. Children of "or" and "and" nodes are visited in order; "not" nodes descend through their expression. All other nodes are leaves.
| Parameter | Description |
|---|
node | The AST node to walk |
visitor | Called once per visited node; return false to skip children |
import { parseOrThrow, walk } from "@filtron/core";
const ast = parseOrThrow('age > 18 AND status = "active"');
const fields: string[] = [];
walk(ast, (node) => {
if ("field" in node) {
fields.push(node.field);
}
});
// fields: ["age", "status"]
function print(node: ASTNode): string;
Prints an AST as canonical Filtron query text
Parsing the output reproduces the AST: parseOrThrow(print(ast)) is deeply equal to ast for any parser-produced tree. Parentheses appear only where a child binds looser than its parent, strings are double-quoted with escapes, and temporal values print behind @. Useful for canonicalizing queries (cache keys, logs) and roundtrip testing.
| Parameter | Description |
|---|
node | The AST node to print |
Returns The canonical query text
import { parseOrThrow, print } from "@filtron/core";
print(parseOrThrow("(a AND (b OR c))"));
// "a AND (b OR c)"
function validateFields(node: ASTNode, allowedFields: readonly string[]): void;
Validates that every field referenced in an AST is in the allowlist
Walks the AST in pre-order and throws on the first field that is not in allowedFields. Useful for guarding hand-built ASTs and untrusted queries before generating filters or SQL.
| Parameter | Description |
|---|
node | The AST node to validate |
allowedFields | Field names that are allowed to appear in the AST |
Throws Error if a field is not in the allowlist
import { parseOrThrow, validateFields } from "@filtron/core";
const ast = parseOrThrow("password = \"hunter2\"");
validateFields(ast, ["name", "age"]);
// Error: Field "password" is not allowed. Allowed fields: name, age
class Lexer {
constructor(input: string);
next(): Token;
}
Lexer for tokenizing Filtron query strings
type Token = StringToken | NumberToken | BooleanToken | TemporalToken | SymbolToken;
Discriminated union of all token types for proper type narrowing
type TokenType = "LPAREN" | "RPAREN" | "LBRACKET" | "RBRACKET" | "COMMA" | "QUESTION" | "DOT" | "DOTDOT" | "MINUS" | "AND" | "OR" | "NOT" | "TRUE" | "FALSE" | "EQ" | "NEQ" | "GT" | "GTE" | "LT" | "LTE" | "LIKE" | "COLON" | "NOT_COLON" | "STRING" | "NUMBER" | "IDENT" | "TEMPORAL" | "EOF";
Token types produced by the lexer
interface StringToken extends TokenBase {
type: "STRING" | "IDENT";
value: string;
}
Tokens with string values (string literals and identifiers)
interface NumberToken extends TokenBase {
type: "NUMBER";
value: number;
}
Tokens with number values
interface BooleanToken extends TokenBase {
type: "TRUE" | "FALSE";
value: boolean;
}
Tokens with boolean values
interface SymbolToken extends TokenBase {
type: "LPAREN" | "RPAREN" | "LBRACKET" | "RBRACKET" | "COMMA" | "QUESTION" | "DOT" | "DOTDOT" | "MINUS" | "AND" | "OR" | "NOT" | "EQ" | "NEQ" | "GT" | "GTE" | "LT" | "LTE" | "LIKE" | "COLON" | "NOT_COLON" | "EOF";
}
Tokens without values (operators, punctuation, keywords, EOF)
type ASTNode = OrExpression | AndExpression | NotExpression | ComparisonExpression | OneOfExpression | ExistsExpression | BooleanFieldExpression;
Root AST node type - represents any valid Filtron expression
type Value = StringValue | NumberValue | BooleanValue | IdentifierValue | RangeValue | DateValue | NowValue;
Value type - represents literal values in expressions
type ComparisonOperator = "=" | "!=" | "~" | ">" | ">=" | "<" | "<=" | ":";
Comparison operators supported
interface OrExpression {
type: "or";
children: ASTNode[];
}
Logical OR expression - combines two or more expressions with OR
Chains are flat: children always has at least two entries and never contains another OrExpression. The parser splices nested OR chains (including parenthesized ones) into a single node.
interface AndExpression {
type: "and";
children: ASTNode[];
}
Logical AND expression - combines two or more expressions with AND
Chains are flat: children always has at least two entries and never contains another AndExpression. The parser splices nested AND chains (including parenthesized ones) into a single node.
interface NotExpression {
type: "not";
expression: ASTNode;
}
Logical NOT expression - negates an expression
interface ComparisonExpression {
type: "comparison";
field: string;
operator: ComparisonOperator;
value: Value;
}
Comparison expression - compares a field to a value using an operator Example: age > 18, status = "active"
interface OneOfExpression {
type: "oneOf";
field: string;
values: Value[];
negated: boolean;
}
Membership expression - checks whether a field matches any value in a list Example: status : ["pending", "approved"], status !: ["deleted"] (negated)
interface ExistsExpression {
type: "exists";
field: string;
negated: boolean;
}
Exists expression - checks whether a field exists (is not null/undefined) Example: email?, -email (negated)
interface BooleanFieldExpression {
type: "booleanField";
field: string;
}
Boolean field expression - shorthand for checking if a boolean field is true Example: verified, premium
interface StringValue {
type: "string";
value: string;
}
String value - a quoted string literal
interface NumberValue {
type: "number";
value: number;
}
Number value - an integer or floating-point number
interface BooleanValue {
type: "boolean";
value: boolean;
}
Boolean value - true or false
interface IdentifierValue {
type: "identifier";
value: string;
}
Identifier value - an unquoted identifier used as a value
type RangeValue = NumberRangeValue | TemporalRangeValue;
Range value - an inclusive interval over numbers or points in time
interface NumberRangeValue {
type: "range";
kind: "number";
min: number;
max: number;
}
Range value over numbers - an inclusive interval Valid with the =, : and != operators only Example: age = 18..65, price != 0..100
interface TemporalRangeValue {
type: "range";
kind: "temporal";
min: TemporalPoint;
max: TemporalPoint;
}
Range value over points in time - an inclusive interval Valid with the =, : and != operators only Example: deployed = @2024-06-01..2024-06-30, created =
interface DateValue {
type: "date";
value: string;
}
Date value - an absolute point in time, written as @<ISO 8601> Example: @2024-06-01, @2024-06-30T14:00:00Z
interface NowValue {
type: "now";
offset: {
amount: number;
unit: DurationUnit;
} | null;
}
Now value - a point in time relative to evaluation, written as
type TemporalPoint = DateValue | NowValue;
A temporal range bound: an absolute date or a now-relative point
type DurationUnit = "s" | "m" | "h" | "d" | "w" | "M" | "y";
Units for now-relative offsets Case-sensitive: m is minutes, M is months