A TypeScript library for defining type-safe data filters. Define your filters once, use them across different data backends.
This is a monorepo containing the following packages:
| Package | Description | NPM |
|---|---|---|
@filter-def/core |
Core types and utilities | |
@filter-def/in-memory |
In-memory filtering with native array methods | |
@filter-def/drizzle |
Drizzle ORM adapter for SQL databases | |
@filter-def/bigquery |
BigQuery adapter for parameterized SQL |
- Type-safe filters: Full TypeScript inference for filter inputs and entity fields
- Multiple backends: Use the same filter patterns for in-memory arrays or SQL databases
- Composable: Combine filters with AND/OR logic
- Custom filters: Define complex business logic with custom filter functions
- Zero lock-in: Each adapter works independently
import { inMemoryFilter } from "@filter-def/in-memory";
interface User {
name: string;
email: string;
age: number;
}
const userFilter = inMemoryFilter<User>().def({
name: { kind: "eq" },
emailContains: { kind: "contains", field: "email" },
minAge: { kind: "gte", field: "age" },
});
// Create predicates for native array methods
const users: User[] = [
/* ... */
];
const results = users.filter(
userFilter({
name: "John",
emailContains: "@example.com",
minAge: 18,
}),
);import { drizzleFilter } from "@filter-def/drizzle";
import { pgTable, text, integer, boolean } from "drizzle-orm/pg-core";
const usersTable = pgTable("users", {
id: integer("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull(),
age: integer("age").notNull(),
});
const userFilter = drizzleFilter(usersTable).def({
name: { kind: "eq" },
emailContains: { kind: "contains", field: "email" },
minAge: { kind: "gte", field: "age" },
});
// Create SQL WHERE clauses
const where = userFilter({
name: "John",
emailContains: "@example.com",
minAge: 18,
});
const results = await db.select().from(usersTable).where(where);import { bigqueryFilter } from "@filter-def/bigquery";
import { BigQuery } from "@google-cloud/bigquery";
const userFilter = bigqueryFilter<User>("myproject.dataset.users").def({
name: { kind: "eq" },
emailContains: { kind: "contains", field: "email" },
minAge: { kind: "gte", field: "age" },
});
// Create parameterized SQL
const where = userFilter({
name: "John",
emailContains: "@example.com",
minAge: 18,
});
const bigquery = new BigQuery();
const [rows] = await bigquery.query({
query: `SELECT * FROM \`myproject.dataset.users\` WHERE ${where.sql}`,
params: where.params,
});All adapters support the same core filter types:
| Kind | Description | Input Type |
|---|---|---|
eq |
Exact equality | Field type |
neq |
Not equal | Field type |
contains |
String contains substring | string |
inArray |
Value is in array | Field type[] |
gt |
Greater than | Field type |
gte |
Greater than or equal | Field type |
lt |
Less than | Field type |
lte |
Less than or equal | Field type |
isNull |
Check if null/undefined | boolean |
isNotNull |
Check if not null/undefined | boolean |
Combine conditions with AND/OR logic:
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" },
],
},
});Each adapter supports custom filters with adapter-specific implementations:
Memory (predicate function):
const filter = inMemoryFilter<User>().def({
hasRole: (user, role: string) => user.roles.includes(role),
});Drizzle (SQL expression):
const filter = drizzleFilter(usersTable).def({
ageDivisibleBy: (divisor: number) =>
sql`${usersTable.age} % ${divisor} = 0`,
});BigQuery (parameterized SQL):
const filter = bigqueryFilter<User>("dataset.users").def({
ageDivisibleBy: (divisor: number) => ({
sql: "MOD(age, @divisor) = 0",
params: { divisor },
}),
});When the filter name matches a field/column name, the field property is optional:
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
});Extract the input type from any filter definition:
import type { InMemoryFilterInput } from "@filter-def/in-memory";
// or
import type { DrizzleFilterInput } from "@filter-def/drizzle";
// or
import type { BigQueryFilterInput } from "@filter-def/bigquery";
const userFilter = inMemoryFilter<User>().def({
name: { kind: "eq" },
minAge: { kind: "gte", field: "age" },
});
type UserFilterInput = InMemoryFilterInput<typeof userFilter>;
// { name?: string; minAge?: number }# For in-memory filtering
npm install @filter-def/in-memory
# For Drizzle ORM
npm install @filter-def/drizzle drizzle-orm
# For BigQuery
npm install @filter-def/bigquery @google-cloud/bigquery@filter-def/in-memoryREADME - Full API docs and examples@filter-def/drizzleREADME - Full API docs and examples@filter-def/bigqueryREADME - Full API docs and examples@filter-def/coreREADME - Core types for adapter authors
MIT