@mcabreradev/filter
Version:
A powerful, SQL-like array filtering library for TypeScript and JavaScript with advanced pattern matching, MongoDB-style operators, deep object comparison, and zero dependencies
838 lines (635 loc) • 25.9 kB
Markdown
# @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.
<p align="center">
<a aria-label="NPM version" href="https://www.npmjs.com/package/@mcabreradev/filter">
<img alt="" src="https://img.shields.io/npm/v/@mcabreradev/filter.svg?style=for-the-badge&labelColor=0869B8">
</a>
<a aria-label="License" href="https://github.com/mcabreradev/filter/blob/main/LICENSE.md">
<img alt="" src="https://img.shields.io/npm/l/@mcabreradev/filter.svg?style=for-the-badge&labelColor=579805">
</a>
<a aria-label="Bundle Size" href="https://bundlephobia.com/package/@mcabreradev/filter">
<img alt="" src="https://img.shields.io/bundlephobia/minzip/@mcabreradev/filter?style=for-the-badge&labelColor=orange">
</a>
<a aria-label="Tree Shaking" href="https://bundlephobia.com/package/@mcabreradev/filter">
<img alt="" src="https://img.shields.io/badge/tree--shaking-friendly-success?style=for-the-badge&labelColor=green">
</a>
<a aria-label="TypeScript" href="#">
<img alt="" src="https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=for-the-badge&labelColor=blue">
</a>
<a aria-label="Zero Dependencies" href="https://bundlephobia.com/package/@mcabreradev/filter">
<img alt="" src="https://img.shields.io/badge/dependencies-0-purple?style=for-the-badge&labelColor=purple">
</a>
</p>
<p align="center">
<a href="#quick-start">Quick Start</a> •
<a href="#why-youll-love-it">Why You'll Love It</a> •
<a href="#examples">Examples</a> •
<a href="https://mcabreradev-filter.vercel.app/playground/">Playground</a> •
<a href="./docs">Documentation</a>
</p>
---
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents
- [The Problem](#the-problem)
- [Quick Start](#quick-start)
- [Install](#install)
- [Your First Filter](#your-first-filter)
- [Why You'll Love It](#why-youll-love-it)
- [🚀 **Blazing Fast**](#-blazing-fast)
- [🎯 **Developer Friendly**](#-developer-friendly)
- [🔧 **Incredibly Flexible**](#-incredibly-flexible)
- [📦 **Production Ready**](#-production-ready)
- [🪶 **Ultra Lightweight**](#-ultra-lightweight)
- [🔒 **Type-Safe by Default**](#-type-safe-by-default)
- [🎨 **Framework Agnostic**](#-framework-agnostic)
- [📊 **Handles Big Data**](#-handles-big-data)
- [Examples](#examples)
- [Basic Filtering](#basic-filtering)
- [MongoDB-Style Operators](#mongodb-style-operators)
- [Array OR Syntax (Intuitive!)](#array-or-syntax-intuitive)
- [Geospatial Queries](#geospatial-queries)
- [Datetime Filtering](#datetime-filtering)
- [Performance Optimization](#performance-optimization)
- [Real-World: E-commerce Search](#real-world-e-commerce-search)
- [Framework Integrations](#framework-integrations)
- [React](#react)
- [Vue](#vue)
- [Svelte](#svelte)
- [Angular](#angular)
- [SolidJS](#solidjs)
- [Preact](#preact)
- [Core Features](#core-features)
- [Supported Operators](#supported-operators)
- [TypeScript Support](#typescript-support)
- [Configuration Options](#configuration-options)
- [Advanced Features](#advanced-features)
- [Lazy Evaluation](#lazy-evaluation)
- [Memoization & Caching](#memoization--caching)
- [Visual Debugging](#visual-debugging)
- [Documentation](#documentation)
- [📖 Complete Guides](#-complete-guides)
- [🎯 Quick Links](#-quick-links)
- [Performance](#performance)
- [Bundle Size](#bundle-size)
- [Browser Support](#browser-support)
- [Migration from v3.x](#migration-from-v3x)
- [Changelog](#changelog)
- [v5.9.2 (Current)](#v592-current)
- [v5.9.1](#v591)
- [v5.8.2](#v582)
- [v5.8.0](#v580)
- [v5.7.0](#v570)
- [v5.6.0](#v560)
- [v5.5.0](#v550)
- [Contributing](#contributing)
- [License](#license)
- [Support](#support)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
---
## 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:**
```typescript
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:**
```typescript
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
```bash
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
```typescript
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 →](https://mcabreradev-filter.vercel.app/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
```typescript
// 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
```typescript
// 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!)
```typescript
// 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
```typescript
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
```typescript
// 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
```typescript
// 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
```typescript
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
```typescript
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
```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
```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
```typescript
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
```tsx
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
```tsx
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 →](./docs/frameworks/index.md)**
---
## 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:
```typescript
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
```typescript
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:
```typescript
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 →](./docs/guide/lazy-evaluation.md)**
### Memoization & Caching
Three-tier LRU caching strategy with automatic TTL eviction:
```typescript
// 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 →](./docs/guide/memoization.md)**
### Visual Debugging
Built-in tree visualization for understanding filter behavior:
```typescript
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 →](./docs/guide/debugging.md)**
---
## Documentation
### 📖 Complete Guides
- **[Getting Started](./docs/guide/getting-started.md)** — Installation and first steps
- **[All Operators](./docs/guide/operators.md)** — Complete operator reference
- **[Geospatial Queries](./docs/guide/geospatial-operators.md)** — Location-based filtering
- **[Datetime Operators](./docs/guide/datetime-operators.md)** — Temporal filtering
- **[Framework Integrations](./docs/frameworks/index.md)** — React, Vue, Svelte, Angular, SolidJS, Preact
- **[Lazy Evaluation](./docs/guide/lazy-evaluation.md)** — Efficient large dataset processing
- **[Memoization & Caching](./docs/guide/memoization.md)** — Performance optimization
- **[Visual Debugging](./docs/guide/debugging.md)** — Debug mode and tree visualization
### 🎯 Quick Links
- [Interactive Playground](https://mcabreradev-filter.vercel.app/playground/) 🎮
- [API Reference](./docs/api/reference.md)
- [Examples](./examples/)
- [Migration Guide](./docs/advanced/migration.md)
- [Performance Benchmarks](./docs/advanced/performance-benchmarks.md)
- [FAQ](./docs/guide/faq.md)
---
## 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 |
```typescript
// 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.
```typescript
// ✅ 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 →](./docs/advanced/migration.md)**
---
## 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 →](./docs/project/changelog.md)**
---
## Contributing
We welcome contributions! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
**Ways to Contribute:**
- Report bugs or request features via [GitHub Issues](https://github.com/mcabreradev/filter/issues)
- Submit pull requests with bug fixes or new features
- Improve documentation
- Share your use cases and examples
---
## License
MIT License — see [LICENSE.md](./LICENSE.md) for details.
Copyright (c) 2025 Miguelangel Cabrera
---
## Support
- 📖 [Complete Documentation](./docs)
- 💬 [GitHub Discussions](https://github.com/mcabreradev/filter/discussions)
- 🐛 [Issue Tracker](https://github.com/mcabreradev/filter/issues)
- ⭐ [Star on GitHub](https://github.com/mcabreradev/filter)
---
<p align="center">
<strong>Made with ❤️ for the JavaScript/TypeScript community</strong>
</p>