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 🙏

© 2026 – Pkg Stats / Ryan Hefner

logical-expression-parser

v2.1.0

Published

Logical expression parser and evaluator, suitable for permissions management

Readme

Logical Expression Parser

npm version

Parses logical expressions like REGISTED&(SPECIAL|INVITED) into an AST and evaluates them with your own token checker. Suitable for permissions management. Written in TypeScript, zero runtime dependencies, ships both CommonJS and ESM builds.

Supported operators

| Operator | Meaning | Precedence | | -------- | -------- | ---------- | | ! | Not | highest | | & | And | middle | | \| | Or | lowest | | (...) | Grouping | — |

  • & binds tighter than |: A&B|C means (A&B)|C.
  • & and | are left-associative.
  • Whitespace between tokens is ignored.
  • Literals are any runs of characters other than whitespace and the operators above (unicode included).

How it works

  1. You require permissions such as REGISTED&(SPECIAL|INVITED).
  2. The parser builds an AST and passes each literal (REGISTED, SPECIAL, INVITED) to your token checking function.
  3. The AST is evaluated with short-circuiting, so your checker is only called for tokens that can still change the result.

Example (CommonJS)

const { parse } = require('logical-expression-parser');

const REQUIREMENTS = 'REGISTED&(SPECIAL|INVITED)';
const LIST_A = ['REGISTED', 'INVITED'];
const LIST_B = ['SPECIAL', 'EXPERT'];

const RESULT_A = parse(REQUIREMENTS, token => LIST_A.includes(token));
const RESULT_B = parse(REQUIREMENTS, token => LIST_B.includes(token));

// RESULT_A: true
// RESULT_B: false

Example (ESM)

import { parse } from 'logical-expression-parser';
// same as above

AST API

import { parseAst, evaluate } from 'logical-expression-parser';

const ast = parseAst('REGISTED&!(BANNED)');
// {
//   type: 'and',
//   left: { type: 'literal', value: 'REGISTED' },
//   right: { type: 'not', operand: { type: 'literal', value: 'BANNED' } }
// }

const allowed = evaluate(ast, token => userPermissions.includes(token));

Async Evaluation

For permission checks requiring database or network calls, use parseAsync or evaluateAsync:

import { parseAsync } from 'logical-expression-parser';

const allowed = await parseAsync('ADMIN | (SPECIAL & !BANNED)', async token => {
  return await checkUserPermission(userId, token);
});

Like the synchronous API, evaluation short-circuits so subsequent async checks are skipped once the outcome is determined.

Custom Operators

You can register custom binary and prefix unary operators with their own precedence, associativity, and evaluation logic using createParser:

import { createParser } from 'logical-expression-parser';

const parser = createParser({
  customOperators: [
    {
      kind: 'binary',
      symbol: '^',
      precedence: 15, // between OR (10) and AND (20)
      associativity: 'left',
      evaluate: (left, right) => left !== right(),
      evaluateAsync: async (left, right) => left !== (await right()),
    },
    {
      kind: 'binary',
      symbol: '->',
      precedence: 5, // looser than OR
      associativity: 'right',
      // Short-circuiting: if left is false, right() is skipped
      evaluate: (left, right) => (!left ? true : right()),
      evaluateAsync: async (left, right) => (!left ? true : await right()),
    },
    {
      kind: 'prefix',
      symbol: '~',
      precedence: 30, // same as '!'
      evaluate: operand => !operand,
      evaluateAsync: async operand => !(await operand),
    },
  ],
});

const allowed = parser.parse('ADMIN ^ (USER -> BETA)', checker);

Built-in Precedence Reference

  • | (OR): 10
  • & (AND): 20
  • ! (NOT): 30

Word operators (e.g. XOR, NAND) are also supported with word-boundary safety (they won't split literals like XOR_GATE).

Types

export type TokenChecker = (token: string) => boolean;
export type AsyncTokenChecker = (token: string) => boolean | Promise<boolean>;

export type LiteralNode = { readonly type: 'literal'; readonly value: string };
export type NotNode = { readonly type: 'not'; readonly operand: AstNode };
export type BinaryNode = {
  readonly type: 'and' | 'or';
  readonly left: AstNode;
  readonly right: AstNode;
};
export type CustomBinaryNode = {
  readonly type: 'custom_binary';
  readonly operator: string;
  readonly left: AstNode;
  readonly right: AstNode;
};
export type CustomUnaryNode = {
  readonly type: 'custom_unary';
  readonly operator: string;
  readonly operand: AstNode;
};
export type AstNode =
  | LiteralNode
  | NotNode
  | BinaryNode
  | CustomBinaryNode
  | CustomUnaryNode;

Errors

Malformed expressions — empty inputs, A&, &A, (), unbalanced parentheses, stray tokens — throw LEPSyntaxError, a SyntaxError subclass carrying the character offset. Invalid argument types (e.g. non-string expression or non-function checker) throw TypeError.

import { parse, LEPSyntaxError } from 'logical-expression-parser';

try {
  parse('A&(B|C', checker);
} catch (error) {
  if (error instanceof LEPSyntaxError) {
    // error.offset === 6
  }
}

Development

Requires Node >= 18.

npm install
npm test   # builds dist/, compiles tests, runs node --test with a c8 coverage report, smoke-checks both dist builds

Sources live in src/, tests in test/; npm run build emits dist/cjs and dist/esm.

Releasing

Releases publish to npm from GitHub Actions when a v-tag is pushed:

  1. Bump version in package.json and commit.
  2. git tag v<version> && git push origin v<version> — the tag must match version exactly (enforced by the workflow).
  3. The workflow builds, runs the full test suite, and publishes with provenance using npm Trusted Publishing (OIDC keyless publishing; configure your repository once under Package Settings → Trusted Publishing on npmjs.com).