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.
In-Memory
Drizzle ORM
BigQuery
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 }));
Multi-Backend Applications
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 ,
});
E-commerce Product Search
Build complex product filtering with price ranges, category filters, and full-text search across multiple fields. interface Product {
name : string ;
description : string ;
price : number ;
category : string ;
inStock : boolean ;
}
const productFilter = inMemoryFilter < Product >(). def ({
search: {
kind: "or" ,
conditions: [
{ kind: "contains" , field: "name" },
{ kind: "contains" , field: "description" },
],
},
priceRange: {
kind: "and" ,
conditions: [
{ kind: "gte" , field: "price" },
{ kind: "lte" , field: "price" },
],
},
category: { kind: "eq" },
inStock: { kind: "eq" },
});
const results = products . filter (
productFilter ({
search: "laptop" ,
priceRange: [ 500 , 2000 ],
inStock: true ,
})
);
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