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

kuery

v2.0.0

Published

MongoDB-style in-memory query engine — zero dependencies, TypeScript, ESM+CJS

Downloads

9,305

Readme

Kuery

MongoDB-style in-memory query engine — zero dependencies, TypeScript, ESM+CJS

Purpose

Kuery is a zero-dependency query engine for filtering in-memory JavaScript collections using MongoDB-compatible query syntax. It compiles queries to optimized native closures, supports full TypeScript with type-safe dot-path queries, and ships dual ESM+CJS.

Installation

npm i kuery

Quick Start

import Kuery from 'kuery';

const users = [
  { id: 1, name: 'Alice', age: 25, role: 'admin', address: { city: 'NYC' } },
  { id: 2, name: 'Bob', age: 17, role: 'user', address: { city: 'LA' } },
  { id: 3, name: 'Carol', age: 30, role: 'mod', address: { city: 'NYC' } },
];

const admins = new Kuery({ role: 'admin' }).find(users);
// [{ id: 1, name: 'Alice', ... }]

const page = new Kuery({ age: { $gte: 18 } })
  .sort({ name: 1 })
  .skip(0)
  .limit(10)
  .find(users);
// [{ id: 1, name: 'Alice', ... }, { id: 3, name: 'Carol', ... }]

// CJS (backward-compatible with v1)
const Kuery = require('kuery');
new Kuery({ id: 1 }).findOne(collection);

Architecture

Kuery processes queries through a three-stage pipeline:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   compile   │────▶│     AST     │────▶│   execute   │
│  (query →   │     │  (ExprNode  │     │  (native    │
│   ExprNode) │     │   tree)     │     │   closures) │
└─────────────┘     └─────────────┘     └─────────────┘
  1. Compile — A MongoDB-style query object is parsed into an ExprNode AST (abstract syntax tree). This is a pure data structure that can be inspected, serialized, or transformed.

  2. AST — The intermediate representation uses three node kinds:

    • literal — constant values
    • path — dot-path field references
    • op — operator applications with child nodes
  3. Execute — The AST is compiled into native JavaScript closures (FilterFn) that run without interpretation overhead. Path resolution handles array traversal, and operators are dispatched via direct function calls.

This design enables:

  • Pre-compilation: compile once, filter millions of documents
  • AST inspection: transform queries, extract diagnostics, build alternative backends
  • Custom operators: extend the engine without modifying core code

API Reference

Kuery class

| Method | Description | |--------|-------------| | new Kuery(query, options?) | Create instance with a filter query | | .skip(n) | Skip first n matched documents | | .limit(n) | Limit results to n documents | | .sort(spec) | Sort by keys (1 = asc, -1 = desc, supports dot-paths) | | .find(collection) | Return all matching documents | | .findOne(collection) | Return exactly one match or throw KueryError | | .test(document) | Return true if document matches the query |

Standalone functions

| Export | Signature | Description | |--------|-----------|-------------| | compileFilter | (query, options?) => FilterFn | Compile query to reusable native filter | | compile | (query) => ExprNode | Compile query to inspectable AST | | evaluate | (ast, scope) => unknown | Evaluate an AST against a document | | find | (collection, query, options?) => T[] | Standalone collection find | | findOne | (collection, query) => T \| undefined | Return first match (no uniqueness assertion) |

Note: Kuery.findOne() asserts exactly one result (throws on 0 or 2+). The standalone findOne() from kuery/collection returns the first match or undefined.

Error handling

import { KueryError } from 'kuery';

try {
  new Kuery({ id: 99 }).findOne(users);
} catch (e) {
  if (e instanceof KueryError) {
    console.log(e.code); // 'KUERY_FIND_ONE'
  }
}

Supported Operators

| Category | Operators | |----------|-----------| | Comparison | $eq, $ne, $gt, $gte, $lt, $lte | | Inclusion | $in, $nin | | Logical | $and, $or, $not, $nor | | Element | $exists | | Array | $elemMatch, $all, $size | | String | $regex (with $options) |

TypeScript — Type-Safe Queries

Kuery ships TypedQuery<T> which provides autocomplete on dot-paths and validates operator values against field types:

import { Kuery, type TypedQuery } from 'kuery';

interface User {
  name: string;
  age: number;
  role: 'admin' | 'user' | 'mod';
  active: boolean;
  address: {
    city: string;
    zip: string;
  };
  tags: string[];
}

// Autocomplete on field paths, type-checked operator values
const query: TypedQuery<User> = {
  age: { $gte: 18 },                          // ✓ number operator on number field
  role: { $in: ['admin', 'mod'] },             // ✓ array of valid role values
  'address.city': { $eq: 'Stockholm' },        // ✓ dot-path with string equality
  active: true,                                // ✓ implicit $eq
};

const results = new Kuery(query).find(users);  // results: User[]

Extending TypedQuery with custom operators

If you register custom operators, you can make them type-safe via module augmentation:

// my-operators.ts
declare module 'kuery' {
  interface CustomFieldOps<V> {
    /** Match documents where a field starts with a prefix (string fields only). */
    $startsWith?: V extends string ? string : never;
    /** Graph traversal operator (string ID fields only). */
    $inGraph?: V extends string ? { relation: string; rootId: string } : never;
  }
}

// Now TypedQuery understands your custom operators:
const query: TypedQuery<User> = {
  name: { $startsWith: 'Al' },  // ✓ type-safe custom operator
};

Performance

v2 compiles queries to native closures instead of piping through lodash/fp. Benchmarks on a MacBook (100k documents):

| Query | v1 (lodash/fp) | v2 cold | v2 hot (pre-compiled) | Speedup | |-------|---------------|---------|----------------------|---------| | { status: 'active' } | 2.37ms | 0.81ms | 0.64ms | 3.7× | | { status: 'active', score: { $gte: 50 } } | 3.08ms | 1.63ms | 1.39ms | 2.2× | | { $or: [{ status: 'active' }, { role: 'admin' }] } | 3.53ms | 1.86ms | 1.70ms | 2.1× | | { 'address.city': 'NYC' } | 2.28ms | 1.09ms | 1.03ms | 2.2× | | { role: { $in: ['admin', 'mod', 'editor'] } } | 1.36ms | 1.05ms | 0.86ms | 1.6× | | { name: { $regex: '^User_1' } } | 1.84ms | 1.27ms | 1.26ms | 1.5× |

Cold = new Kuery(query).find(collection) (compile + execute each call) Hot = compileFilter(query) once, then collection.filter(fn) repeatedly

Pre-compiling filters is recommended for hot paths (event handlers, stream processing, repeated evaluations).

Advanced Usage

Pre-compiled filters

import { compileFilter } from 'kuery/filter';

const isEligible = compileFilter<User>({ age: { $gte: 18 }, active: true });

// Reuse across millions of evaluations
stream.filter(isEligible);
events.filter(isEligible);

Custom operators

import { Kuery, OperatorRegistry } from 'kuery';

const registry = new OperatorRegistry();
registry.register(
  { name: '$between', arity: 2 },
  (args) => {
    const [value, [min, max]] = args;
    return typeof value === 'number' && value >= min && value <= max;
  }
);

const q = new Kuery({ score: { $between: [10, 50] } }, { registry });

Failure tracing / diagnostics

import { compile } from 'kuery/compile';
import { evaluateWithTrace } from 'kuery/trace';

const ast = compile({ age: { $gte: 18 }, role: 'admin' });
const { result, traces } = evaluateWithTrace(ast, { age: 15, role: 'user' });
// traces: [{ path: 'age', operator: '$gte', expected: 18, actual: 15 }, ...]

Sub-path imports

For tree-shaking or when you only need specific functionality:

import { compileFilter } from 'kuery/filter';
import { compile } from 'kuery/compile';
import { evaluate } from 'kuery/evaluate';
import { OperatorRegistry } from 'kuery/operators';
import { KueryError } from 'kuery/errors';
import { find, findOne } from 'kuery/collection';

Migrating from v1

Breaking Changes

  1. $exists checks key presence, not truthiness

    • v1: { field: { $exists: true } } matched if !!doc.field was truthy
    • v2: matches if the key exists at all (even if value is null, 0, "", false)
    • Migration: use { field: { $ne: null } } if you need truthiness semantics
  2. Array fields now match element-wise (MongoDB-compatible)

    • v1: { tags: { $in: ['a'] } } on { tags: ['a','b'] } → no match
    • v2: matches if any array element satisfies the condition
  3. Falsy values in dot-path traversal (bugfix)

    • v1: { 'items.score': 0 } silently failed for score: 0 inside arrays
    • v2: correctly matches
  4. Error type: KueryError replaces generic Error

    • findOne throws KueryError with a .code property
    • instanceof Error still true; existing catch handlers work
  5. lodash removed — zero runtime dependencies

Non-Breaking Additions

  • TypeScript types with TypedQuery<T> and dot-path autocomplete
  • ESM + CJS dual format
  • New operators: $nor, $all, $size
  • Custom operator registry with type-safe augmentation
  • Pre-compiled filter functions (1.5–3.7× faster)
  • Prototype pollution protection
  • Failure trace diagnostics

License

ISC