Skip to main content

Overview

Version 2.0 of filter-def introduced breaking changes to simplify the API and improve type safety. This guide will help you migrate your existing v1 code to v2.

Breaking Changes

Type Name Changes

The most significant changes in v2 are type naming simplifications:
All public-facing type names were simplified in v2 for better developer experience.
v1 Typev2 TypeDescription
PredicateCreatorFilterMain filter function type
InputForPredicateCreator<T>FilterInput<T>Type for filter inputs

Before (v1):

import { inMemoryFilter } from "@filter-def/in-memory";
import type { InputForPredicateCreator } from "@filter-def/in-memory";

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

// Type extraction in v1
type UserFilterInput = InputForPredicateCreator<typeof userFilter>;

After (v2):

import { inMemoryFilter } from "@filter-def/in-memory";
import type { InMemoryFilterInput } from "@filter-def/in-memory";

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

// Type extraction in v2
type UserFilterInput = InMemoryFilterInput<typeof userFilter>;

Adapter-Specific Type Names

Each adapter now exports its own input type utility:
import type { InMemoryFilterInput } from "@filter-def/in-memory";

const userFilter = inMemoryFilter<User>().def({ /* ... */ });
type UserInput = InMemoryFilterInput<typeof userFilter>;

Null and Undefined Handling

In v2.0, filter inputs no longer accept null or undefined as explicit values for better type safety:

Before (v1):

// This was allowed in v1
const where = userFilter({
    name: null, // Accepted but confusing behavior
});

After (v2):

// In v2, null/undefined inputs are type errors
const where = userFilter({
    // name: null, // ❌ Type error in v2
});

// Simply omit the field instead
const where = userFilter({
    // Empty object or omit undefined fields
});
If you need to check for null values, use the isNull or isNotNull filter kinds instead of passing null as a filter value.

Migration Steps

1

Update Package Versions

Update all filter-def packages to v2 in your package.json:
pnpm update @filter-def/core @filter-def/in-memory @filter-def/drizzle @filter-def/bigquery
Or manually:
package.json
{
  "dependencies": {
    "@filter-def/in-memory": "^2.0.0",
    "@filter-def/drizzle": "^2.0.0",
    "@filter-def/bigquery": "^2.0.0"
  }
}
2

Update Type Imports

Find and replace all type imports:
// Find:
import type { InputForPredicateCreator } from "@filter-def/in-memory";

// Replace with:
import type { InMemoryFilterInput } from "@filter-def/in-memory";
For Drizzle:
// Find:
import type { InputForPredicateCreator } from "@filter-def/drizzle";

// Replace with:
import type { DrizzleFilterInput } from "@filter-def/drizzle";
For BigQuery:
// Find:
import type { InputForPredicateCreator } from "@filter-def/bigquery";

// Replace with:
import type { BigQueryFilterInput } from "@filter-def/bigquery";
3

Update Type Aliases

Update all type alias definitions:
// Before (v1)
type UserFilterInput = InputForPredicateCreator<typeof userFilter>;

// After (v2)
type UserFilterInput = InMemoryFilterInput<typeof userFilter>;
// or DrizzleFilterInput / BigQueryFilterInput depending on adapter
4

Remove Null Filter Values

Search for any code passing null or undefined as filter values and remove them:
// Before (v1)
const results = users.filter(
    userFilter({
        name: searchTerm ?? null, // Remove null fallback
        minAge: ageFilter ?? undefined, // Remove undefined fallback
    })
);

// After (v2)
const filterInput: Partial<UserFilterInput> = {};
if (searchTerm) filterInput.name = searchTerm;
if (ageFilter) filterInput.minAge = ageFilter;

const results = users.filter(userFilter(filterInput));

// Or use object spreading
const results = users.filter(
    userFilter({
        ...(searchTerm && { name: searchTerm }),
        ...(ageFilter && { minAge: ageFilter }),
    })
);
5

Test Your Changes

Run your type checker and tests:
tsc --noEmit
npm test
Fix any type errors that appear. Most errors will be related to:
  • Old type names that need updating
  • Null/undefined values being passed to filters

New Features in v2

While migrating, consider adopting these new v2 features:

Nested Field Support

v2 adds support for nested field paths in @filter-def/in-memory and @filter-def/bigquery:
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" },
});

const results = employees.filter(
    employeeFilter({
        firstName: "John",
        city: "New York",
        minLat: 40.7,
    })
);
Nested fields are not supported in @filter-def/drizzle as Drizzle operates on flat table columns.

Case-Insensitive Contains

The contains filter now supports case-insensitive matching:
const userFilter = inMemoryFilter<User>().def({
    emailContains: {
        kind: "contains",
        field: "email",
        caseInsensitive: true, // New in v2
    },
});

const results = users.filter(
    userFilter({
        emailContains: "EXAMPLE", // Matches "example", "Example", "EXAMPLE", etc.
    })
);

Troubleshooting

Type Errors After Migration

You need to update your type imports. Replace:
import type { InputForPredicateCreator } from "@filter-def/in-memory";
With the adapter-specific type:
import type { InMemoryFilterInput } from "@filter-def/in-memory";
// or DrizzleFilterInput, BigQueryFilterInput
v2 no longer accepts null as a filter value. Instead of:
userFilter({ name: null })
Either omit the field or use the isNull filter:
// Option 1: Omit the field
userFilter({})

// Option 2: Use isNull filter
const userFilter = inMemoryFilter<User>().def({
    nameIsNull: { kind: "isNull", field: "name" },
});

userFilter({ nameIsNull: true })
The PredicateCreator type was renamed to adapter-specific filter types. Replace:
import type { PredicateCreator } from "@filter-def/in-memory";
type MyFilter = PredicateCreator<User, any>;
With:
import type { InMemoryFilter } from "@filter-def/in-memory";
type MyFilter = InMemoryFilter<User, any>;

Need Help?

If you encounter issues during migration:
  1. Check the GitHub issues for similar problems
  2. Review the API Reference for detailed type information
  3. Open a new issue with your migration question

Upgrade Checklist

Use this checklist to ensure a complete migration:
  • Update all @filter-def/* packages to v2.x
  • Replace InputForPredicateCreator with adapter-specific input types
  • Replace PredicateCreator with adapter-specific filter types
  • Remove all null and undefined filter values
  • Update type aliases and function signatures
  • Run TypeScript type checker (tsc --noEmit)
  • Run test suite to verify behavior
  • Consider adopting new v2 features (nested fields, case-insensitive contains)
  • Update documentation and examples in your codebase

Build docs developers (and LLMs) love