Skip to content

A portable, framework-agnostic search query parser with Gmail-like syntax support. Zero dependencies, TypeScript-first, and optimized for performance.

License

Notifications You must be signed in to change notification settings

muhgholy/search-query-parser

Repository files navigation

Search Query Parser

npm version CI License: MIT

A portable, framework-agnostic search query parser with Gmail-like syntax support. Zero dependencies, TypeScript-first, and optimized for performance.

Features

  • 🚀 Zero dependencies - Lightweight and fast
  • 📝 Gmail-like syntax - Familiar search operators
  • 🔧 Framework-agnostic - Works anywhere (Node.js, browser, edge)
  • 📦 TypeScript-first - Full type safety
  • Optimized - Returns simple array for easy iteration
  • 🎯 Extensible - Custom operators support

Installation

npm install @muhgholy/search-query-parser
yarn add @muhgholy/search-query-parser
pnpm add @muhgholy/search-query-parser

Usage

import { parse } from "@muhgholy/search-query-parser";

const terms = parse('"Promo Code" -spam from:newsletter after:-7d');

// Returns: TParsedTerm[]
// [
//   { type: 'phrase', value: 'Promo Code', negated: false },
//   { type: 'text', value: 'spam', negated: true },
//   { type: 'from', value: 'newsletter', negated: false },
//   { type: 'after', value: '-7d', negated: false, date: Date }
// ]

// Simple iteration
for (const term of terms) {
	switch (term.type) {
		case "text":
		case "phrase":
			// Handle text search
			break;
		case "from":
			// Handle from filter
			break;
		case "after":
			if (term.date) {
				// Use resolved date
			}
			break;
		case "or":
			// Handle OR logic
			// term.terms contains the operands
			break;
		case "group":
			// Handle group
			// term.terms contains the inner terms
			break;
	}
}

Supported Syntax

Text Search

Syntax Description Example
word Plain text search hello
"phrase" Exact phrase match "hello world"
-word Exclude term -spam
-"phrase" Exclude phrase -"unsubscribe here"
( ... ) Grouping (term1 term2)
OR Logical OR term1 OR term2

Lists & Arrays

Comma-separated values are automatically treated as OR conditions.

Syntax Description Example
key:val1,val2 Value 1 OR Value 2 to:john,jane
key:"a","b" Quoted list to:"John Doe","Jane"
-key:val1,val2 NOT val1 AND NOT val2 -from:spam,marketing

Operators

Operator Aliases Description Example
from: f:, sender: From address/name from:john@example.com
to: t:, recipient: To address/name to:jane
subject: subj:, s: Subject line subject:meeting
body: content:, b: Body content body:invoice
has: - Has property has:attachment
is: - Status filter is:unread
in: folder:, box: Folder/mailbox in:inbox
label: tag:, l: Label/tag label:important
header-k: hk: Header key header-k:X-Custom
header-v: hv: Header value header-v:"custom value"
date: d: Date/Range date:2024-01-01
before: b4:, older: Before date before:2024-12-31
after: af:, newer: After date after:2024-01-01
size: larger:, smaller: Size filter size:>1mb

Date Filters

Syntax Description Example
date:YYYY-MM-DD Specific date date:2024-01-01
date:Start-End Date range date:2024-01-01-2024-12-31
after:YYYY-MM-DD After date (absolute) after:2024-01-01
before:YYYY-MM-DD Before date (absolute) before:2024-12-31
after:-Nd After N days ago after:-7d
after:-Nh After N hours ago after:-24h
after:-Nw After N weeks ago after:-2w
after:-Nm After N months ago after:-1m
after:-Ny After N years ago after:-1y
after:"natural" Natural language after:"last week"

Supported natural dates: today, yesterday, tomorrow, last week, last month, last year, this week, this month, this year

Size Filters

Syntax Description Example
size:>N Larger than N bytes size:>1mb
size:<N Smaller than N bytes size:<100kb
size:N Equal to N bytes size:500

Supported units: b, kb, mb, gb

API Reference

parse(input: string, options?: TParserOptions): TParseResult

Parse a search query string into an array of terms.

const terms = parse('"hello world" from:john -spam', {
	operatorsAllowed: ["from", "to"], // Only allow specific operators
	// OR
	operatorsDisallowed: ["size"], // Block specific operators
	// Custom operators
	operators: [{ name: "priority", aliases: ["p"], type: "string", allowNegation: true }],
});

tokenize(input: string): TToken[]

Low-level tokenizer for custom parsing needs.

const tokens = tokenize('from:john "hello world"');

parseDate(value: string): { date: Date } | null

Parse date strings (absolute, relative, natural).

parseDate("-7d"); // { date: Date (7 days ago) }
parseDate("2024-01-01"); // { date: Date }
parseDate("last week"); // { date: Date }

escapeRegex(str: string): string

Escape special regex characters.

escapeRegex("hello.*world"); // 'hello\\.\\*world'

validate(input: string): { valid: boolean; errors: string[] }

Validate search query syntax.

validate('"unclosed quote'); // { valid: false, errors: ['Unmatched quote: "'] }

hasTerms(input: string): boolean

Check if search string has any terms.

hasTerms(""); // false
hasTerms("hello"); // true

summarize(input: string): string[]

Get human-readable summary of search query.

summarize('"Promo" from:newsletter after:-7d');
// ['Exact: "Promo"', 'From: newsletter', 'After: 12/6/2024']

Types

type TDefaultTermType =
	| "text" // Plain text
	| "phrase" // Exact phrase
	| "from" // From filter
	| "to" // To filter
	| "subject" // Subject filter
	| "body" // Body filter
	| "header-k" // Header key
	| "header-v" // Header value
	| "has" // Has property
	| "is" // Status filter
	| "in" // Folder filter
	| "before" // Before date
	| "after" // After date
	| "label" // Label filter
	| "size" // Size filter
	| "or" // Logical OR
	| "group"; // Parenthesized group

type TTermType = TDefaultTermType;

type TParsedTerm<T extends string = TTermType> = {
	type: T;
	value: string;
	negated: boolean;
	date?: Date; // Resolved date (for date types)
	dateRange?: {
		// Resolved date range
		start: Date;
		end: Date;
	};
	size?: {
		// Parsed size (for size type)
		op: "gt" | "lt" | "eq";
		bytes: number;
	};
	terms?: TParsedTerm<T>[]; // For 'or' and 'group' types
};

type TParseResult<T extends string = TTermType> = TParsedTerm<T>[];

type TOperatorDef<T extends string = TTermType> = {
	name: string; // Operator name (becomes term type)
	aliases: string[]; // Alternative names
	type: "string" | "date" | "size"; // Value parsing type
	allowNegation: boolean; // Whether negation is allowed
};

type TParserOptions<T extends string = TTermType> = {
	operators?: TOperatorDef<T>[];
	caseSensitive?: boolean;
	operatorsAllowed?: string[];
	operatorsDisallowed?: string[];
};

Examples

MongoDB Integration

import { parse, escapeRegex } from "@muhgholy/search-query-parser";

function buildMongoQuery(searchQuery: string) {
	const terms = parse(searchQuery);
	const conditions = [];

	for (const term of terms) {
		const regex = { $regex: escapeRegex(term.value), $options: "i" };

		switch (term.type) {
			case "text":
			case "phrase":
				conditions.push({
					$or: [{ title: term.negated ? { $not: regex } : regex }, { content: term.negated ? { $not: regex } : regex }],
				});
				break;
			case "from":
				conditions.push({ "from.email": regex });
				break;
			case "after":
				if (term.date) {
					conditions.push({ createdAt: { $gte: term.date } });
				}
				break;
		}
	}

	return conditions.length ? { $and: conditions } : {};
}

SQL Integration

import { parse, escapeRegex } from "@muhgholy/search-query-parser";

function buildSQLWhere(searchQuery: string) {
	const terms = parse(searchQuery);
	const clauses = [];
	const params = [];

	for (const term of terms) {
		switch (term.type) {
			case "text":
				clauses.push(term.negated ? "(title NOT LIKE ? AND content NOT LIKE ?)" : "(title LIKE ? OR content LIKE ?)");
				params.push(`%${term.value}%`, `%${term.value}%`);
				break;
			case "after":
				if (term.date) {
					clauses.push("created_at >= ?");
					params.push(term.date.toISOString());
				}
				break;
		}
	}

	return { where: clauses.join(" AND "), params };
}

License

MIT © Muhammad Gholy

About

A portable, framework-agnostic search query parser with Gmail-like syntax support. Zero dependencies, TypeScript-first, and optimized for performance.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •