ExSift

MongoDB-style query filtering for Elixir collections.

ExSift is an Elixir library inspired by sift.js that brings MongoDB's powerful query syntax to Elixir. Filter lists, maps, and any enumerable with an expressive, familiar query language.

Features

Installation

Add ex_sift to your list of dependencies in mix.exs:

def deps do
  [
    {:ex_sift, "~> 0.1.0"}
  ]
end

Quick Start

data = [
  %{name: "Alice", age: 30, city: "NYC"},
  %{name: "Bob", age: 25, city: "SF"},
  %{name: "Charlie", age: 35, city: "NYC"}
]

# Simple equality
ExSift.filter(data, %{city: "NYC"})
# => [%{name: "Alice", ...}, %{name: "Charlie", ...}]

# Comparison operators
ExSift.filter(data, %{age: %{"$gt" => 28}})
# => [%{name: "Alice", age: 30, ...}, %{name: "Charlie", age: 35, ...}]

# Multiple conditions
ExSift.filter(data, %{city: "NYC", age: %{"$gte" => 30}})
# => [%{name: "Alice", ...}, %{name: "Charlie", ...}]

Supported Operators

Comparison Operators

Logical Operators

Array Operators

Other Operators

Advanced Usage

Nested Properties

Use dot notation to query nested maps:

data = [
  %{user: %{profile: %{age: 30, verified: true}}},
  %{user: %{profile: %{age: 25, verified: false}}}
]

ExSift.filter(data, %{"user.profile.age" => %{"$gt" => 28}})
# => [%{user: %{profile: %{age: 30, ...}}}]

Complex Queries

Combine multiple operators for powerful filtering:

data = [
  %{name: "Alice", age: 30, status: "active", tags: ["admin"]},
  %{name: "Bob", age: 25, status: "inactive", tags: ["user"]},
  %{name: "Charlie", age: 35, status: "active", tags: ["admin", "moderator"]}
]

ExSift.filter(data, %{
  "$and" => [
    %{age: %{"$gte" => 25, "$lte" => 35}},
    %{status: "active"},
    %{tags: %{"$in" => ["admin"]}}
  ]
})
# => [%{name: "Alice", ...}, %{name: "Charlie", ...}]

Utility Functions

ExSift provides several utility functions beyond filter/2:

data = [%{a: 1}, %{a: 2}, %{a: 3}]

# Test a single item
ExSift.test(%{a: 1}, %{a: 1})  # => true

# Find first matching item
ExSift.find(data, %{a: 2})  # => %{a: 2}

# Check if any match
ExSift.any?(data, %{a: %{"$gt" => 2}})  # => true

# Check if all match
ExSift.all?(data, %{a: %{"$gte" => 1}})  # => true

# Count matches
ExSift.count(data, %{a: %{"$lt" => 3}})  # => 2

# Compile query for reuse
tester = ExSift.compile(%{a: %{"$gt" => 1}})
tester.(%{a: 2})  # => true
tester.(%{a: 1})  # => false

Architecture

ExSift is built with three main modules:

The library leverages Elixir's pattern matching and protocol system for extensible, type-safe query operations.

Comparison with sift.js

ExSift is inspired by sift.js but adapted for Elixir's functional programming paradigm:

Feature sift.js ExSift
Language JavaScript/TypeScript Elixir
Architecture Operation classes with state Pattern matching + pure functions
Extensibility Custom operations via options Protocol-based (future)
Type Safety TypeScript generics Dialyzer typespecs
Immutability Depends on usage Built-in (Elixir default)

Performance

ExSift uses single-pass filtering with early termination where possible. All operations are implemented as pure functions without side effects.

For large datasets, consider:

Testing

Run the test suite:

mix test

ExSift includes 40+ tests covering:

License

MIT License - See LICENSE file for details

Acknowledgments

Inspired by sift.js by Craig Condon.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.