Skip to main content

What is filter-def?

filter-def is a TypeScript library that lets you define type-safe data filters once and use them across different data backends. Write your filter logic in one place, then apply it to in-memory arrays, SQL databases via Drizzle ORM, or BigQuery - all with full type inference.
import { inMemoryFilter } from "@filter-def/in-memory";

const userFilter = inMemoryFilter<User>().def({
    name: { kind: "eq" },
    emailContains: { kind: "contains", field: "email" },
    minAge: { kind: "gte", field: "age" },
});

const results = users.filter(
    userFilter({ name: "John", minAge: 18 })
);

Key Benefits

Type Safety

Full TypeScript inference for filter inputs and entity fields. Catch errors at compile time, not runtime.

Backend Agnostic

Define filters once, use them with in-memory arrays, SQL databases, or BigQuery. Zero lock-in.

Composable

Combine filters with AND/OR logic. Build complex queries from simple, reusable pieces.

Zero Dependencies

Lightweight and framework-agnostic. Each adapter depends only on its target backend.

Core Features

Type-Safe Filter Definitions

Define filters with full TypeScript inference. The library automatically infers field types from your entity schema and validates filter inputs at compile time.
interface User {
    name: string;
    email: string;
    age: number;
    isActive: boolean;
}

const userFilter = inMemoryFilter<User>().def({
    name: { kind: "eq" },              // infers string
    emailContains: { kind: "contains", field: "email" },  // infers string
    minAge: { kind: "gte", field: "age" },  // infers number
});

// TypeScript knows the exact shape of filter inputs
const predicate = userFilter({
    name: "John",        // ✓ string
    emailContains: "@example.com",  // ✓ string
    minAge: 18,          // ✓ number
    // minAge: "18",     // ✗ Type error: string not assignable to number
});

Field Inference

When a filter name matches a field name, the field property is automatically inferred:
const filter = inMemoryFilter<User>().def({
    name: { kind: "eq" },     // field: "name" inferred
    email: { kind: "contains" },  // field: "email" inferred
    minAge: { kind: "gte", field: "age" },  // explicit field required
});

Nested Field Access

Filter on deeply nested object properties using dot-separated paths:
interface Employee {
    name: { first: string; last: string };
    address: { city: string; geo: { lat: number; lng: number } };
}

const employeeFilter = inMemoryFilter<Employee>().def({
    firstName: { kind: "eq", field: "name.first" },
    city: { kind: "contains", field: "address.city" },
    minLat: { kind: "gte", field: "address.geo.lat" },
});
Nested fields are supported by @filter-def/in-memory and @filter-def/bigquery. The Drizzle adapter does not support nested fields since it operates on flat table columns.

Boolean Logic (AND/OR)

Combine multiple conditions with logical operators:
const filter = inMemoryFilter<User>().def({
    // OR: match any condition
    searchTerm: {
        kind: "or",
        conditions: [
            { kind: "contains", field: "name" },
            { kind: "contains", field: "email" },
        ],
    },

    // AND: match all conditions
    ageRange: {
        kind: "and",
        conditions: [
            { kind: "gte", field: "age" },
            { kind: "lte", field: "age" },
        ],
    },
});

const results = users.filter(
    filter({
        searchTerm: "john",
        ageRange: [18, 65],  // min and max
    })
);

Custom Filter Logic

Define complex business logic with custom filter functions. Each adapter supports custom filters with adapter-specific implementations:
const filter = inMemoryFilter<User>().def({
    hasRole: (user, role: string) => user.roles.includes(role),
});

Use Cases

Build REST or GraphQL APIs with consistent filtering across endpoints. Define filter schemas once and use them for both validation and data fetching.
const userFilter = inMemoryFilter<User>().def({
    name: { kind: "contains" },
    minAge: { kind: "gte", field: "age" },
    isActive: { kind: "eq" },
});

app.get("/api/users", async (req, res) => {
    const { name, minAge, isActive } = req.query;
    const where = userFilter({ name, minAge, isActive });
    const users = await db.select().from(usersTable).where(where);
    res.json(users);
});
Use the same filter logic for in-memory testing and production databases. Prototype with arrays, deploy with SQL.
// Development: test with in-memory data
const memoryFilter = inMemoryFilter<User>().def({
    isActive: { kind: "eq" },
});
const testUsers = mockUsers.filter(memoryFilter({ isActive: true }));

// Production: query database with same logic
const dbFilter = drizzleFilter(usersTable).def({
    isActive: { kind: "eq" },
});
const prodUsers = await db.select()
    .from(usersTable)
    .where(dbFilter({ isActive: true }));
Build applications that support multiple data sources. Use PostgreSQL for transactional data and BigQuery for analytics with the same filter definitions.
// Shared filter schema
const filterDef = {
    createdAfter: { kind: "gte", field: "createdAt" },
    status: { kind: "eq" },
};

// Query PostgreSQL via Drizzle
const pgFilter = drizzleFilter(ordersTable).def(filterDef);
const recentOrders = await db.select()
    .from(ordersTable)
    .where(pgFilter({ createdAfter: yesterday }));

// Query BigQuery for analytics
const bqFilter = bigqueryFilter<Order>().def(filterDef);
const where = bqFilter({ createdAfter: lastMonth });
const [analytics] = await bigquery.query({
    query: `SELECT status, COUNT(*) FROM orders WHERE ${where.sql} GROUP BY status`,
    params: where.params,
});

Available Packages

@filter-def/core

Core types and utilities for building custom adapters

@filter-def/in-memory

Filter JavaScript arrays with native array methods

@filter-def/drizzle

Generate SQL WHERE clauses for Drizzle ORM

@filter-def/bigquery

Create parameterized BigQuery SQL queries

Next Steps

Installation

Install filter-def packages for your project

Quick Start

Build your first filter in 5 minutes

Build docs developers (and LLMs) love