aloof
v2.0.0
Published
Array of Objects Filtering
Maintainers
Readme
node-aloof
Array Logical Object Oriented Filtering - A powerful TypeScript library for filtering arrays of objects with a fluent, chainable API.
Installation
npm install aloof
# or
pnpm add aloof
# or
yarn add aloofUsage
Basic Filtering
import filter from 'aloof';
// Sample data
const data = [
{ id: 1, name: 'John', gender: 'male', age: 28 },
{ id: 2, name: 'Jane', gender: 'female', age: 32 },
{ id: 3, name: 'Bob', gender: 'male', age: 22 }
];
// Simple equality filter
const males = filter(data, {
gender: {
comparison: '=',
value: 'male'
}
});
// Result: [{ id: 1, name: 'John', gender: 'male', age: 28 }, { id: 3, name: 'Bob', gender: 'male', age: 22 }]Chainable API
// Using the chainable API with method syntax
const result = filter.with(data)
.equals('gender', 'female')
.select('name');
// Result: [{ name: 'Jane' }]
// Multiple conditions (AND)
const youngMales = filter.with(data)
.equals('gender', 'male')
.lessThan('age', 25)
.select();
// Result: [{ id: 3, name: 'Bob', gender: 'male', age: 22 }]
// OR conditions
const johnsOrFemales = filter.with(data)
.equals('name', 'John')
.or()
.equals('gender', 'female')
.select();
// Result: [{ id: 1, name: 'John', gender: 'male', age: 28 }, { id: 2, name: 'Jane', gender: 'female', age: 32 }]Comparison Operators
Aloof supports numerous comparison operators:
equals(==): Equality checknotEquals(!=): Inequality checkstarts: Checks if string starts with valueends: Checks if string ends with valuecontains: Checks if string contains valuematch: Regex matchgreater/greaterThan(>): Greater thangreaterEquals/greaterThanEquals(>=): Greater than or equalless/lessThan(<): Less thanlessEquals/lessThanEquals(<=): Less than or equalbetween: Between two values (exclusive)betweenEquals: Between two values (inclusive)outside: Outside two values (exclusive)outsideEquals: Outside two values (inclusive)truthy: Checks if value is truthyfalsy/falsey: Checks if value is falsyin: Checks if value is in array or string
// Examples
filter.with(data).greaterThan('age', 25).select(); // Age > 25
filter.with(data).betweenEquals('age', 20, 30).select(); // 20 <= Age <= 30
filter.with(data).in('id', [1, 3]).select(); // id in [1, 3]
filter.with(data).starts('name', 'J').select(); // Name starts with JProjection (Select)
Select specific fields from the filtered results:
const names = filter.with(data)
.equals('gender', 'male')
.select('name', 'age');
// Result: [{ name: 'John', age: 28 }, { name: 'Bob', age: 22 }]Sorting
Sort the filtered results:
// Ascending sort
const ascending = filter.with(data)
.sort('age')
.select();
// Result: [{ id: 3, ... age: 22 }, { id: 1, ... age: 28 }, { id: 2, ... age: 32 }]
// Descending sort (prefix with -)
const descending = filter.with(data)
.sort('-age')
.select();
// Result: [{ id: 2, ... age: 32 }, { id: 1, ... age: 28 }, { id: 3, ... age: 22 }]
// Multiple sort fields
const multiSort = filter.with(data)
.sort('gender', 'age')
.select();
// Sorts by gender, then by age
// With coercion
const coercedSort = filter.with(data)
.sort('age', Number)
.select();
// Coerces values to numbers before sortingGrouping
Group data by a specific field:
const grouped = filter.with(data)
.group('gender')
.select();
// Result will include a groups property:
// grouped.groups = {
// gender: {
// male: [{ id: 1, ... }, { id: 3, ... }],
// female: [{ id: 2, ... }]
// }
// }Aggregation Functions
// Count records
const count = filter.with(data).equals('gender', 'male').count();
// Result: 2
// Sum values
const totalAge = filter.with(data).sum('age');
// Result: 82 (28 + 32 + 22)
// Average
const avgAge = filter.with(data).avg('age');
// Result: 27.33...
// Min/Max values
const minAge = filter.with(data).min('age'); // 22
const maxAge = filter.with(data).max('age'); // 32Nested Properties
Access nested object properties with dot notation:
const nested = [
{ id: 1, profile: { name: 'John', gender: 'male' } },
{ id: 2, profile: { name: 'Jane', gender: 'female' } }
];
const result = filter.with(nested)
.equals('profile.name', 'John')
.select();
// Result: [{ id: 1, profile: { name: 'John', gender: 'male' } }]TypeScript Support
This library is written in TypeScript and provides type definitions out of the box.
Development
Building
# Install dependencies
npm install
# Build the library
npm run build
# Build with watch mode
npm run build:watchTesting
Tests are run using Vitest:
# Run tests
npm test
# Run tests with watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
# Type checking
npm run typecheckPerformance
Aloof has been designed with performance in mind, particularly when compared to similar libraries. Below are some anecdotal benchmark results comparing Aloof's filtering performance against Sift.js and native JavaScript approaches.
Benchmark Results
These benchmarks were run using simple equality filters on a dataset with 10,000 iterations:
| Method | Average Speed (filters/sec) | Average Time (ms/filter) | |--------|---------------------------|------------------------| | Aloof with cached filter function | ~8,000 | ~0.15 | | Aloof without cached function | ~6,300 | ~0.17 | | Sift.js | ~2,100 | ~0.48 | | Native Array.filter | ~47,000 | ~0.02 | | Direct for loop | ~74,000 | ~0.01 |
Key Takeaways
Aloof vs Sift.js: Aloof consistently performs 3-4x faster than Sift.js for equivalent filtering operations.
Code Generation Advantage: Aloof's approach of dynamically generating specialized filter functions provides better performance than general-purpose libraries like Sift.js while maintaining flexibility.
Native Methods: As expected, direct for loops and native Array.filter methods still provide the best raw performance, but lack the flexibility and API that Aloof provides.
Cached Function Benefit: Using Aloof's cached filter functions can provide a modest performance improvement for repeated filtering operations.
Note on Benchmarks
These benchmarks are anecdotal and represent specific test conditions. Performance will vary based on:
- Size and complexity of the dataset
- Complexity of filter operations
- Hardware and JavaScript runtime environment
- Other concurrent processes
For most applications, the flexibility and expressiveness of Aloof's API will outweigh the raw performance difference between it and native JavaScript methods. However, for extremely performance-critical applications with simple filtering needs, consider using native JavaScript loops or filter methods.
For detailed benchmark methodology or to run the benchmarks yourself, see the test/bench directory.
License
MIT
