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

@mcabreradev/filter

v5.10.1

Published

A powerful, SQL-like array filtering library for TypeScript and JavaScript with advanced pattern matching, MongoDB-style operators, deep object comparison, and zero dependencies

Downloads

577

Readme

@mcabreradev/filter

Filter arrays like a pro. A powerful, SQL-like array filtering library for TypeScript with advanced pattern matching, MongoDB-style operators, deep object comparison, geospatial queries, and zero dependencies.


Table of Contents


The Problem

Tired of writing complex filter logic? Stop wrestling with nested Array.filter() chains and verbose conditionals. Write clean, declarative filters that read like queries.

Before — the usual mess:

const results = data.filter(
  (item) =>
    item.age >= 18 &&
    item.status === 'active' &&
    (item.role === 'admin' || item.role === 'moderator') &&
    item.email.endsWith('@company.com') &&
    item.createdAt >= thirtyDaysAgo,
);

After — clean and declarative:

const results = filter(data, {
  age: { $gte: 18 },
  status: 'active',
  role: ['admin', 'moderator'],
  email: { $endsWith: '@company.com' },
  createdAt: { $gte: thirtyDaysAgo },
});

Same result. 70% less code. 100% more readable.


Quick Start

Install

npm install @mcabreradev/filter
# or
pnpm add @mcabreradev/filter
# or
yarn add @mcabreradev/filter

Requirements: Node.js >= 20, TypeScript 5.0+ (optional)

Your First Filter

import { filter } from '@mcabreradev/filter';

const users = [
  { name: 'Alice', age: 30, city: 'Berlin', active: true },
  { name: 'Bob', age: 25, city: 'London', active: false },
  { name: 'Charlie', age: 35, city: 'Berlin', active: true },
];

// Simple string search — scans all fields
const berlinUsers = filter(users, 'Berlin');
// → [{ name: 'Alice', ... }, { name: 'Charlie', ... }]

// Object matching — AND logic across fields
const activeBerlinUsers = filter(users, { city: 'Berlin', active: true });
// → [{ name: 'Alice', ... }]

// MongoDB-style operators
const adults = filter(users, { age: { $gte: 18 } });
// → All users

// SQL-like wildcards
const startsWithAl = filter(users, 'Al%');
// → [{ name: 'Alice', ... }]

🎮 Try it in the Playground →


Why You'll Love It

🚀 Blazing Fast

  • 530x faster on repeated queries with optional LRU caching
  • 500x faster with lazy evaluation for large datasets
  • Compiled predicates and regex patterns cached automatically

🎯 Developer Friendly

  • Intuitive API — reads like English
  • SQL-like wildcards (%, _) you already know
  • Full TypeScript generics with intelligent autocomplete

🔧 Incredibly Flexible

  • Four filtering strategies: strings, objects, operators, predicates
  • Combine them seamlessly in a single expression
  • Works with any data shape — flat, nested, arrays

📦 Production Ready

  • 1,004+ tests ensuring bulletproof reliability
  • Zero runtime dependencies (only Zod for optional validation)
  • Battle-tested in production applications
  • MIT licensed

🪶 Ultra Lightweight

  • Full package: 12KB gzipped
  • Core only: 8.4KB gzipped
  • Zero mandatory dependencies
  • Tree-shakeable — only pay for what you use

🔒 Type-Safe by Default

  • Built with strict TypeScript
  • Catch errors at compile time, not runtime
  • Full IntelliSense for operators based on field types

🎨 Framework Agnostic

  • First-class hooks: React, Vue, Svelte, Angular, SolidJS, Preact
  • Debounced search, pagination, and reactive state out of the box
  • SSR compatible: Next.js, Nuxt, SvelteKit

📊 Handles Big Data

  • Generator-based lazy evaluation for millions of records
  • Early exit — stop processing when you have enough results
  • LRU caches with TTL prevent memory leaks in long-running apps

Examples

Basic Filtering

// String matching — searches all string properties
filter(products, 'Laptop');

// Exact field matching — AND logic
filter(products, { category: 'Electronics', price: { $lt: 1000 } });

// SQL wildcard patterns
filter(users, '%alice%'); // contains 'alice'
filter(users, 'Al%'); // starts with 'Al'
filter(users, '%son'); // ends with 'son'
filter(users, 'J_hn'); // single-char wildcard

// Predicate functions — full control
filter(users, (u) => u.score > 90 && u.verified);

MongoDB-Style Operators

// Comparison
filter(products, { price: { $gte: 100, $lte: 500 } });
filter(products, { rating: { $gt: 4 }, stock: { $ne: 0 } });

// Array membership
filter(products, { category: { $in: ['Electronics', 'Books'] } });
filter(products, { tags: { $contains: 'sale' } });
filter(products, { sizes: { $size: 3 } });

// String matching
filter(users, {
  email: { $endsWith: '@company.com' },
  name: { $startsWith: 'John' },
  bio: { $regex: /developer/i },
});

// Logical combinators
filter(products, {
  $and: [{ inStock: true }, { $or: [{ rating: { $gte: 4.5 } }, { price: { $lt: 50 } }] }],
});

// Negate with $not
filter(users, { role: { $not: 'banned' } });

Array OR Syntax (Intuitive!)

// Pass an array → automatic OR logic, no $in needed
filter(products, { category: ['Electronics', 'Books'] });
// Same as: { category: { $in: ['Electronics', 'Books'] } }

// Combine across fields
filter(users, {
  city: ['Berlin', 'Paris', 'London'],
  role: ['admin', 'moderator'],
});

Geospatial Queries

import { filter, type GeoPoint } from '@mcabreradev/filter';

const userLocation: GeoPoint = { lat: 52.52, lng: 13.405 };

// Find restaurants within 5km rated 4.5+
filter(restaurants, {
  location: { $near: { center: userLocation, maxDistanceMeters: 5000 } },
  rating: { $gte: 4.5 },
});

// Bounding box search
filter(places, {
  location: {
    $geoBox: {
      topLeft: { lat: 53.0, lng: 13.0 },
      bottomRight: { lat: 52.0, lng: 14.0 },
    },
  },
});

Datetime Filtering

// Events in next 7 days
filter(events, { date: { $upcoming: { days: 7 } } });

// Recent activity (last 24 hours)
filter(logs, { createdAt: { $recent: { hours: 24 } } });

// Weekday events during business hours
filter(events, {
  date: { $dayOfWeek: [1, 2, 3, 4, 5] }, // Mon–Fri
  startTime: { $timeOfDay: { start: 9, end: 17 } }, // 9am–5pm
});

// Users of age 18–65
filter(users, { birthDate: { $age: { min: 18, max: 65 } } });

// Weekend-only events
filter(events, { date: { $isWeekend: true } });

// Events before a deadline
filter(tasks, { dueDate: { $isBefore: new Date('2025-12-31') } });

Performance Optimization

// LRU caching — 530x faster on repeat queries
const results = filter(largeDataset, expression, {
  enableCache: true,
  orderBy: { field: 'price', direction: 'desc' },
  limit: 100,
});

// Lazy evaluation — process millions of records without loading all into memory
import { filterFirst, filterExists, filterCount, filterLazy } from '@mcabreradev/filter';

const first10 = filterFirst(millionRecords, { premium: true }, 10);
const hasAdmin = filterExists(users, { role: 'admin' }); // exits on first match
const activeCount = filterCount(users, { active: true }); // no array allocated

Real-World: E-commerce Search

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
  brand: string;
  rating: number;
  inStock: boolean;
  tags: string[];
}

// Affordable, highly-rated electronics in stock
const results = filter<Product>(products, {
  category: 'Electronics',
  price: { $lte: 1000 },
  rating: { $gte: 4.5 },
  inStock: true,
});

// Full-text search with brand filter
const searchResults = filter<Product>(products, {
  name: { $contains: 'laptop' },
  brand: ['Apple', 'Dell', 'HP'],
  price: { $gte: 500, $lte: 2000 },
});

// Sorted and paginated results
const page1 = filter<Product>(
  products,
  { category: 'Electronics', inStock: true },
  {
    orderBy: [
      { field: 'price', direction: 'asc' },
      { field: 'rating', direction: 'desc' },
    ],
    limit: 20,
  },
);

Framework Integrations

First-class hooks and composables — reactive, debounced, paginated, ready to drop in:

React

import { useFilter, useDebouncedFilter, usePaginatedFilter } from '@mcabreradev/filter/react';

function UserList() {
  const { filtered, isFiltering } = useFilter(users, { active: true });

  return (
    <ul>
      {filtered.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

// Debounced live search
function SearchBox() {
  const [query, setQuery] = useState('');
  const { filtered, isPending } = useDebouncedFilter(users, query, { delay: 300 });

  return (
    <>
      <input onChange={e => setQuery(e.target.value)} />
      {isPending ? <Spinner /> : filtered.map(u => <User key={u.id} user={u} />)}
    </>
  );
}

Vue

<script setup lang="ts">
import { ref } from 'vue';
import { useFilter } from '@mcabreradev/filter/vue';

const expression = ref({ active: true });
const { filtered, isFiltering } = useFilter(users, expression);
</script>

<template>
  <ul>
    <li v-for="user in filtered" :key="user.id">{{ user.name }}</li>
  </ul>
</template>

Svelte

<script lang="ts">
  import { writable } from 'svelte/store';
  import { useFilter } from '@mcabreradev/filter/svelte';

  const expression = writable({ active: true });
  const { filtered } = useFilter(users, expression);
</script>

{#each $filtered as user}
  <p>{user.name}</p>
{/each}

Angular

import { FilterService } from '@mcabreradev/filter/angular';

@Component({
  providers: [FilterService],
  template: `
    @for (user of filterService.filtered(); track user.id) {
      <div>{{ user.name }}</div>
    }
  `,
})
export class UserListComponent {
  filterService = inject(FilterService<User>);
}

SolidJS

import { useFilter } from '@mcabreradev/filter/solidjs';

function UserList() {
  const { filtered } = useFilter(
    () => users,
    () => ({ active: true }),
  );
  return <For each={filtered()}>{(u) => <div>{u.name}</div>}</For>;
}

Preact

import { useFilter } from '@mcabreradev/filter/preact';

function UserList() {
  const { filtered } = useFilter(users, { active: true });
  return (
    <div>
      {filtered.map((u) => (
        <div key={u.id}>{u.name}</div>
      ))}
    </div>
  );
}

Every integration includes:

  • ✅ Full TypeScript generics
  • ✅ Debounced search hook with isPending state
  • ✅ Pagination hook with nextPage, prevPage, goToPage
  • ✅ SSR compatible
  • ✅ 100% test coverage

📖 Complete Framework Guide →


Core Features

Supported Operators

| Category | Operators | | -------------- | ------------------------------------------------------------------------------------------------------- | | Comparison | $gt $gte $lt $lte $eq $ne | | Array | $in $nin $contains $size | | String | $startsWith $endsWith $contains $regex $match | | Logical | $and $or $not | | Geospatial | $near $geoBox $geoPolygon | | Datetime | $recent $upcoming $dayOfWeek $timeOfDay $age $isWeekday $isWeekend $isBefore $isAfter |

18+ operators covering every filtering scenario you'll encounter.

TypeScript Support

Full type safety — autocomplete shows only valid operators for each field type:

interface Product {
  name: string;
  price: number;
  tags: string[];
}

filter<Product>(products, {
  price: { $gte: 100 }, // ✅ number operators
  name: { $contains: '' }, // ✅ string operators
  tags: { $size: 3 }, // ✅ array operators
  price: { $contains: '' }, // ❌ TypeScript error — string op on number field
});

Configuration Options

filter(data, expression, {
  caseSensitive: false, // default: false
  maxDepth: 3, // nested object traversal depth (1–10)
  enableCache: true, // LRU result caching (530x speedup)
  orderBy: 'price', // sort field or array of fields
  limit: 10, // cap result count
  debug: true, // print expression tree to console
  verbose: true, // detailed per-item evaluation logs
  showTimings: true, // execution time per operator
  enablePerformanceMonitoring: true, // collect performance metrics
});

Advanced Features

Lazy Evaluation

Process large datasets without loading everything into memory:

import { filterLazy, filterFirst, filterExists, filterCount } from '@mcabreradev/filter';

// Generator — pull items one by one, exit any time
const lazy = filterLazy(millionRecords, { active: true });
for (const item of lazy) {
  process(item);
  if (shouldStop) break; // ← zero wasted work
}

// Grab first N matches
const top10 = filterFirst(users, { premium: true }, 10);

// Check existence — exits on first match
const hasBanned = filterExists(users, { role: 'banned' });

// Count matches — no array allocated
const total = filterCount(orders, { status: 'pending' });

| Scenario | Array.filter | filterLazy / filterFirst | | ----------------------- | ------------ | ------------------------ | | First match in 1M items | ~50ms | ~0.1ms | | Memory for 1M items | ~80MB | ~0KB | | Early exit | ❌ | ✅ |

📖 Lazy Evaluation Guide →

Memoization & Caching

Three-tier LRU caching strategy with automatic TTL eviction:

// First call — compiles predicates, runs filter, stores result
const results = filter(largeDataset, { age: { $gte: 18 } }, { enableCache: true });

// Subsequent calls — returns cached result instantly
const same = filter(largeDataset, { age: { $gte: 18 } }, { enableCache: true });

| Scenario | Without Cache | With Cache | Speedup | | ----------------------- | ------------- | ---------- | --------- | | Simple query, 10K items | 5.3ms | 0.01ms | 530x | | Regex pattern | 12.1ms | 0.02ms | 605x | | Complex nested query | 15.2ms | 0.01ms | 1520x |

Caches are bounded (LRU, max 500 entries each) and auto-expire after 5 minutes — safe for long-running servers.

📖 Memoization Guide →

Visual Debugging

Built-in tree visualization for understanding filter behavior:

filter(users, { city: 'Berlin', age: { $gte: 18 } }, { debug: true });

// Console output:
// ┌─ Filter Debug Tree
// │  Expression: {"city":"Berlin","age":{"$gte":18}}
// │  Matched: 3/10 items (30.0%)
// │  Execution time: 0.42ms
// ├─ ✓ city = "Berlin"         [3 matches]
// └─ ✓ age >= 18               [3 matches]

📖 Debug Guide →


Documentation

📖 Complete Guides

🎯 Quick Links


Performance

| Technique | Benefit | | --------------------- | --------------------------------------------------- | | Early-exit operators | Skip remaining items on first mismatch | | LRU result cache | 530x–1520x speedup on repeated queries | | LRU predicate cache | Compiled predicates reused across calls | | LRU regex cache | Compiled patterns reused, bounded to 500 entries | | Lazy generators | 500x faster when you don't need all results | | Absolute TTL eviction | Stale entries removed after 5 min — no memory leaks |

// Enable all optimizations at once
filter(data, expression, { enableCache: true });

// Maximum efficiency for large datasets
const first100 = filterFirst(millionRecords, { active: true }, 100);

Bundle Size

| Import | Size (gzipped) | Tree-Shakeable | | --------------- | -------------- | -------------- | | Full | 12 KB | ✅ | | Core only | 8.4 KB | ✅ | | React hooks | 9.2 KB | ✅ | | Lazy evaluation | 5.4 KB | ✅ |


Browser Support

Works in all modern browsers and Node.js:

  • Node.js: >= 20
  • Browsers: Chrome, Firefox, Safari, Edge (latest versions)
  • TypeScript: >= 5.0
  • Module Systems: ESM, CommonJS

Migration from v3.x

Good news: v5.x is 100% backward compatible. All v3.x code continues to work.

// ✅ All v3.x syntax still works
filter(data, 'string');
filter(data, { prop: 'value' });
filter(data, (item) => true);
filter(data, '%pattern%');

// ✅ New in v5.x
filter(data, { age: { $gte: 18 } });
filter(data, expression, { enableCache: true, limit: 50 });

📖 Migration Guide →


Changelog

v5.9.2 (Current)

  • 🧹 Refactor: Removed Svelte integration exports, dependencies, and related docs from the package.
  • 🔧 CI/CD: Hardened npm publish workflow with safer version resolution and improved release handling.
  • 📦 Release Reliability: Synced lockfile with dependency changes to avoid CI failures with --frozen-lockfile.
  • Publishing: Improved release pipeline behavior so versioning and GitHub Releases run more consistently.

v5.9.1

  • 🚀 Release: Published patch release with workflow and release-process fixes.
  • 🏷️ Versioning: Stabilized tag/version flow to avoid collisions with existing release tags.

v5.8.2

  • 🐛 Bug Fix: Wildcard regex now correctly escapes all special characters (., +, *, ?, (, [, ^, etc.) — patterns like %.txt or a.b% no longer silently break
  • 🐛 Bug Fix: $timeOfDay with start > end (e.g. { start: 22, end: 5 }) now correctly fails validation instead of silently never matching
  • 🐛 Bug Fix: React useDebouncedFilter now reacts to delay prop changes — previously the initial delay was frozen for the hook's lifetime
  • 🔒 Validation: limit option now validated by schema — negative or non-integer values throw a clear configuration error
  • 🔒 Validation: debug, verbose, showTimings, colorize, enablePerformanceMonitoring options now validated by schema
  • Performance: Pattern-matching regex cache now delegates to the shared LRU MemoizationManager — the previously unbounded Map is gone
  • Performance: LRU cache TTL is now absolute (expire 5 min after creation) instead of sliding — entries can no longer live forever under heavy load
  • 🧹 Code Quality: Svelte pagination replaced subscribe()() anti-pattern with idiomatic get() from svelte/store
  • Tests: 1,004+ tests — added coverage for every bug fixed in this release

v5.8.0

  • 🎨 New Framework Integrations: Angular, SolidJS, and Preact support
  • 🔢 Limit Option: New limit configuration to restrict result count
  • 📊 OrderBy Option: Sort filtered results by field(s) in ascending or descending order
  • ✅ 993+ tests with comprehensive coverage

v5.7.0

  • 🅰️ Angular: Services and Pipes with Signals support
  • 🔷 SolidJS: Signal-based reactive hooks
  • Preact: Lightweight hooks API

v5.6.0

  • 🌍 Geospatial Operators: Location-based filtering with $near, $geoBox, $geoPolygon
  • 📅 Datetime Operators: Temporal filtering with $recent, $upcoming, $dayOfWeek, $age

v5.5.0

  • 🎨 Array OR Syntax: Intuitive array-based OR filtering
  • 🐛 Visual Debugging: Built-in debug mode with expression tree visualization
  • 🎮 Interactive Playground: Online playground for testing filters

📖 Full Changelog →


Contributing

We welcome contributions! Please read our Contributing Guide for details.

Ways to Contribute:

  • Report bugs or request features via GitHub Issues
  • Submit pull requests with bug fixes or new features
  • Improve documentation
  • Share your use cases and examples

License

MIT License — see LICENSE.md for details.

Copyright (c) 2025 Miguelangel Cabrera


Support