npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@abaktiar/ql-parser

v1.5.0

Published

Framework-agnostic QL (Query Language) parser and builder for creating complex queries with support for logical operators, parameterized functions, and ORDER BY clauses

Readme

@abaktiar/ql-parser

A powerful, framework-agnostic QL (Query Language) parser and builder for creating complex queries with support for logical operators, functions, parentheses grouping, and ORDER BY clauses.

🌐 Live Demo - See the parser in action with the React component!

npm version TypeScript License: MIT

🎉 What's New in v1.5.0

🔧 Enhanced Function System - Comprehensive parameterized function support with type validation
🧮 Improved Expression Trees - Better parsing for complex nested expressions and function calls
🎯 Parameter Validation - Robust validation for function parameters with descriptive error messages
📝 Extended Documentation - Comprehensive examples and migration guides for parameterized functions

✨ Features

  • 🔍 Complete Query Parsing - Parse complex QL queries into structured AST
  • 🧮 Expression Trees - Build hierarchical expression trees with proper operator precedence
  • 🔧 Query Building - Convert parsed queries to MongoDB, SQL, or custom formats
  • 📝 TypeScript Support - Full type safety with comprehensive interfaces
  • 🎯 Parentheses Grouping - Support for complex condition grouping
  • 📊 ORDER BY Support - Sorting clauses with ASC/DESC directions
  • 🔄 Function Support - Built-in functions like currentUser(), now(), etc.
  • Zero Dependencies - Lightweight with no external dependencies
  • 🌐 Framework Agnostic - Works with any JavaScript framework or vanilla JS

📦 Installation

npm install @abaktiar/ql-parser
yarn add @abaktiar/ql-parser
pnpm add @abaktiar/ql-parser

🚀 Quick Start

import { QLParser, QLInputConfig } from '@abaktiar/ql-parser';

// Define your field configuration
const config: QLInputConfig = {
  fields: [
    {
      name: 'status',
      displayName: 'Status',
      type: 'option',
      operators: ['=', '!=', 'IN', 'NOT IN'],
      options: [
        { value: 'open', label: 'Open' },
        { value: 'closed', label: 'Closed' },
        { value: 'pending', label: 'Pending' }
      ]
    },
    {
      name: 'assignee',
      displayName: 'Assignee',
      type: 'user',
      operators: ['=', '!=', 'IS EMPTY', 'IS NOT EMPTY']
    },
    {
      name: 'created',
      displayName: 'Created Date',
      type: 'date',
      operators: ['=', '>', '<', '>=', '<='],
      sortable: true
    }
  ],
  maxSuggestions: 10,
  caseSensitive: false,
  allowParentheses: true,
  allowOrderBy: true,
  allowFunctions: true
};

// Create parser instance
const parser = new QLParser(config);

// Parse a query
const query = parser.parse('status = "open" AND (assignee = currentUser() OR assignee IS EMPTY) ORDER BY created DESC');

console.log(query);
// Output: Structured QLQuery object with parsed expressions and order by clause

📖 Core Concepts

Query Structure

A QL query consists of:

  1. WHERE clause - Conditions and logical operators
  2. ORDER BY clause - Sorting specifications (optional)
status = "open" AND assignee = currentUser() ORDER BY created DESC, priority ASC

Supported Operators

Comparison Operators

  • = - Equals
  • != - Not equals
  • > - Greater than
  • < - Less than
  • >= - Greater than or equal
  • <= - Less than or equal

Text Operators

  • ~ - Contains (case-insensitive)
  • !~ - Does not contain

List Operators

  • IN - Value in list
  • NOT IN - Value not in list

Null Operators

  • IS EMPTY - Field is empty/null
  • IS NOT EMPTY - Field is not empty/null

Logical Operators

  • AND - Logical AND
  • OR - Logical OR
  • NOT - Logical NOT (prefix)

Functions

Built-in functions for dynamic values:

Parameterless Functions

  • currentUser() - Current user identifier
  • now() - Current timestamp
  • today() - Today's date
  • startOfWeek() - Start of current week
  • endOfWeek() - End of current week
  • startOfMonth() - Start of current month
  • endOfMonth() - End of current month

Parameterized Functions

  • daysAgo(days) - Date N days ago from today
    daysAgo(30)  // 30 days ago
    daysAgo(7)   // 7 days ago
  • daysFromNow(days) - Date N days from today
    daysFromNow(14)  // 14 days from now
  • dateRange(start, end) - Date range between two dates
    dateRange("2023-01-01", "2023-12-31")
  • userInRole(role) - Users with specific role
    userInRole("admin")
    userInRole("manager")
  • projectsWithPrefix(prefix) - Projects with name prefix
    projectsWithPrefix("PROJ")
    projectsWithPrefix("DEV")

Function Usage in Queries

Functions can be used in various contexts:

  • Single conditions: created >= daysAgo(30)
  • IN lists: assignee IN (currentUser(), userInRole("admin"))
  • Complex expressions: (created >= daysAgo(30) AND assignee = currentUser()) OR priority = "High"
  • Nested functions: dateRange("2023-01-01", daysAgo(30))

🔧 API Reference

QLParser

Main parser class for tokenizing and parsing QL queries.

class QLParser {
  constructor(config: QLInputConfig)

  // Tokenize input string into tokens
  tokenize(input: string): QLToken[]

  // Parse input string into structured query
  parse(input: string): QLQuery
}

QLExpressionParser

Builds hierarchical expression trees from tokens.

class QLExpressionParser {
  // Parse tokens into expression tree
  parse(tokens: QLToken[]): QLExpression | null
}

Query Builder Functions

Convert parsed queries to different formats:

// Convert to MongoDB query
function toMongooseQuery(query: QLQuery): object

// Convert to SQL WHERE clause
function toSQLQuery(query: QLQuery): string

// Print expression as readable string
function printExpression(expression: QLExpression): string

// Count total conditions in query
function countConditions(query: QLQuery): number

💡 Usage Examples

Basic Parsing

const parser = new QLParser(config);

// Simple condition
const simple = parser.parse('status = "open"');

// Multiple conditions with AND
const multiple = parser.parse('status = "open" AND priority = "high"');

// Conditions with OR
const withOr = parser.parse('status = "open" OR status = "pending"');

// Complex grouping with parentheses
const complex = parser.parse('(status = "open" OR status = "pending") AND assignee = currentUser()');

Working with Functions

// Using parameterless functions
const withFunctions = parser.parse(`
  assignee = currentUser() AND
  created >= startOfWeek() AND
  created <= endOfWeek()
`);

// Using parameterized functions
const withParams = parser.parse(`
  assignee = userInRole("admin") AND
  created >= daysAgo(30) AND
  updated <= daysFromNow(7)
`);

// Functions with multiple parameters
const multiParam = parser.parse('created = dateRange("2023-01-01", "2023-12-31")');

// Functions in lists
const functionList = parser.parse('assignee IN (currentUser(), userInRole("manager"), "john.doe")');

// Nested function calls
const nested = parser.parse('created = dateRange("2023-01-01", daysAgo(30))');

// Complex expressions with functions
const complex = parser.parse(`
  (assignee = currentUser() OR assignee = userInRole("admin")) AND
  created >= daysAgo(30) AND
  project = projectsWithPrefix("PROJ")
`);

ORDER BY Clauses

// Single sort field
const singleSort = parser.parse('status = "open" ORDER BY created DESC');

// Multiple sort fields
const multiSort = parser.parse('status = "open" ORDER BY priority ASC, created DESC');

// Sort only (no WHERE clause)
const sortOnly = parser.parse('ORDER BY created DESC');

Converting to Different Formats

import { toMongooseQuery, toSQLQuery, printExpression } from '@abaktiar/ql-parser';

const query = parser.parse('status = "open" AND assignee = currentUser()');

// Convert to MongoDB query
const mongoQuery = toMongooseQuery(query);
console.log(mongoQuery);
// Output: { status: "open", assignee: "currentUser()" }

// Convert to SQL
const sqlQuery = toSQLQuery(query);
console.log(sqlQuery);
// Output: "status = 'open' AND assignee = 'currentUser()'"

// Print as readable string
if (query.where) {
  const readable = printExpression(query.where);
  console.log(readable);
  // Output: "status = "open" AND assignee = currentUser()"
}

Function Configuration

You can define custom functions with parameters in your configuration:

const config: QLInputConfig = {
  fields: [...],
  allowFunctions: true,
  functions: [
    {
      name: 'currentUser',
      displayName: 'currentUser()',
      description: 'Current logged in user',
    },
    {
      name: 'daysAgo',
      displayName: 'daysAgo(days)',
      description: 'Date N days ago from today',
      parameters: [{
        name: 'days',
        type: 'number',
        required: true,
        description: 'Number of days'
      }]
    },
    {
      name: 'userInRole',
      displayName: 'userInRole(role)',
      description: 'Users with specific role',
      parameters: [{
        name: 'role',
        type: 'text',
        required: true,
        description: 'User role name'
      }]
    },
    {
      name: 'dateRange',
      displayName: 'dateRange(start, end)',
      description: 'Date range between two dates',
      parameters: [
        {
          name: 'startDate',
          type: 'date',
          required: true,
          description: 'Start date'
        },
        {
          name: 'endDate',
          type: 'date',
          required: true,
          description: 'End date'
        }
      ]
    }
  ]
};

Function Parameter Types

  • text - String parameters (quoted in queries)
  • number - Numeric parameters (unquoted)
  • date - Date parameters (quoted)
  • boolean - Boolean parameters (true/false)

Error Handling

const query = parser.parse('invalid query syntax');

if (!query.valid) {
  console.log('Parse errors:', query.errors);
  // Handle parsing errors
}

Working with Tokens

// Get detailed token information
const tokens = parser.tokenize('status = "open" AND assignee = currentUser()');

tokens.forEach(token => {
  console.log(`${token.type}: "${token.value}" at position ${token.start}-${token.end}`);
});

🎯 Advanced Usage

Custom Field Types

const config: QLInputConfig = {
  fields: [
    {
      name: 'customField',
      displayName: 'Custom Field',
      type: 'text', // or 'number', 'date', 'datetime', 'boolean', 'option', 'multiselect', 'user'
      operators: ['=', '!=', '~', '!~'],
      description: 'A custom field for demonstration'
    }
  ],
  // ... other config options
};

Complex Expressions

// Nested parentheses
const nested = parser.parse(`
  (status = "open" OR status = "pending") AND
  (assignee = currentUser() OR (assignee IS EMPTY AND priority = "high"))
`);

// Mixed operators
const mixed = parser.parse(`
  status IN ("open", "pending") AND
  title ~ "urgent" AND
  created >= startOfWeek() AND
  assignee IS NOT EMPTY
`);

Expression Tree Traversal

function traverseExpression(expr: QLExpression, depth = 0): void {
  const indent = '  '.repeat(depth);

  if (expr.type === 'condition') {
    console.log(`${indent}Condition: ${expr.field} ${expr.operator} ${expr.value}`);
  } else if (expr.type === 'group') {
    console.log(`${indent}Group (${expr.operator}):`);
    expr.conditions.forEach(child => traverseExpression(child, depth + 1));
  }
}

const query = parser.parse('(status = "open" OR status = "pending") AND assignee = currentUser()');
if (query.where) {
  traverseExpression(query.where);
}

📚 Type Definitions

Core Types

// Field configuration
interface QLField {
  name: string;
  displayName: string;
  type: FieldType;
  operators: OperatorType[];
  sortable?: boolean;
  description?: string;
  options?: QLValue[];
  asyncValueSuggestions?: boolean;
}

// Parsed query result
interface QLQuery {
  where?: QLExpression;
  orderBy?: QLOrderBy[];
  raw: string;
  valid: boolean;
  errors: string[];
}

// Expression tree node
interface QLExpression {
  type: 'condition' | 'group';
  // ... additional properties based on type
}

🤝 Related Packages

📄 License

MIT © abaktiar

🐛 Issues & Support

Please report issues on GitHub Issues.