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

aloof

v2.0.0

Published

Array of Objects Filtering

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 aloof

Usage

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 check
  • notEquals (!=): Inequality check
  • starts: Checks if string starts with value
  • ends: Checks if string ends with value
  • contains: Checks if string contains value
  • match: Regex match
  • greater/greaterThan (>): Greater than
  • greaterEquals/greaterThanEquals (>=): Greater than or equal
  • less/lessThan (<): Less than
  • lessEquals/lessThanEquals (<=): Less than or equal
  • between: Between two values (exclusive)
  • betweenEquals: Between two values (inclusive)
  • outside: Outside two values (exclusive)
  • outsideEquals: Outside two values (inclusive)
  • truthy: Checks if value is truthy
  • falsy/falsey: Checks if value is falsy
  • in: 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 J

Projection (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 sorting

Grouping

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'); // 32

Nested 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:watch

Testing

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 typecheck

Performance

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

  1. Aloof vs Sift.js: Aloof consistently performs 3-4x faster than Sift.js for equivalent filtering operations.

  2. 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.

  3. 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.

  4. 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