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

  •  
  •