npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@azghr/filterkit

v0.3.13

Published

Framework-agnostic, type-safe filtering for TypeScript: one filter definition that runs in memory on the client and translates to Prisma, MongoDB, or SQL on the server.

Downloads

300

Readme

filterkit

npm MIT License

Framework-agnostic, type-safe filtering for TypeScript. Define a filter once — run it in memory on the client (React, Vue, Angular, vanilla) or translate it into a database query on the server (Prisma, MongoDB, raw SQL, Drizzle, or your own adapter).

  • Zero runtime dependencies, ~2.5 KB gzipped core
  • Full type safety: fields, operators, and values are all inferred from your data type, including nested objects and arrays ("posts.comments.author" autocompletes)
  • Serializable filter AST: plain JSON — build on the client, POST it, validate, translate on the server
  • Extensible: register custom operators at runtime, type them via declaration merging, translate them per adapter
  • Tree-shakable: adapters live behind subpath exports; if you never import filterkit/adapters/sql, it never ships

The problem

Building and maintaining consistent filtering logic across client and server is complex. You need to:

  • Ensure filters work the same in browser memory and database queries
  • Maintain type safety across the stack
  • Support multiple database backends with different query syntaxes
  • Validate untrusted filter inputs from API clients

Each adapter (Prisma, MongoDB, SQL) has its own query format, forcing you to duplicate logic or build fragile translation layers.

Install

npm install @azghr/filterkit
# or
pnpm add @azghr/filterkit
# or
yarn add @azghr/filterkit

With adapters:

# For Drizzle ORM
pnpm add @azghr/filterkit drizzle-orm

# For Prisma
pnpm add @azghr/filterkit @prisma/client

# For MongoDB
pnpm add @azghr/filterkit mongodb

# SQL adapter has no additional dependencies

Use

Quick start

import { createFilter, createEngine } from "@azghr/filterkit";

interface User {
  name: string;
  age: number;
  email: string;
  profile: { city: string; score: number };
  posts: { title: string; likes: number }[];
}

const filter = createFilter<User>()
  .where("age", "greaterThanOrEquals", 18)
  .where("email", "endsWith", "@example.com")
  .or((b) => b.where("profile.city", "equals", "Berlin"))
  .build();

const sort = [{ field: "age", direction: "desc" }];

// Client side: filter + sort in memory
const engine = createEngine<User>();
const visible = engine.filter(users, filter, { sort });

// The same filter is plain JSON — send it to your API
await fetch("/api/users/search", {
  method: "POST",
  body: JSON.stringify({ filter, sort }),
});

Server-side translation

// Server side: translate the SAME filter + sort into a database query
import { toDrizzleWhere, toDrizzleOrderBy } from "@azghr/filterkit/adapters/drizzle";
import { toPrismaWhere, toPrismaOrderBy } from "@azghr/filterkit/adapters/prisma";
import { toMongoQuery, toMongoSort } from "@azghr/filterkit/adapters/mongo";
import { toSqlWhere, toSqlOrderBy } from "@azghr/filterkit/adapters/sql";

// Drizzle
const results = await db
  .select()
  .from(users)
  .where(toDrizzleWhere(filter, { column: (f) => users[f] }))
  .orderBy(...toDrizzleOrderBy(sort, (f) => users[f]));

// Prisma
const users = await prisma.user.findMany({
  where: toPrismaWhere(filter, { listRelations: ["posts"] }),
  orderBy: toPrismaOrderBy(sort),
});

// MongoDB
const docs = await db.collection("users")
  .find(toMongoQuery(filter))
  .sort(toMongoSort(sort))
  .toArray();

// SQL
const { text, params } = toSqlWhere(filter, { placeholder: "$n" });
const orderBy = toSqlOrderBy(sort);
const rows = await pg.query(`SELECT * FROM users WHERE ${text} ORDER BY ${orderBy}`, params);

API

Core functions

| Export | Purpose | | --- | --- | | createFilter<T>() | Fluent, typed builder → Filter<T> AST | | createEngine<T>(opts?) | Engine with custom operators + getter cache | | engine.filter(data, filter, { sort }) | Filter + sort arrays in memory | | validateFilter(input, opts?) | Validate untrusted ASTs before translating | | applyFilter / matchesFilter | One-shot helpers, built-ins only |

Detailed API surface

| Export | Purpose | | --- | --- | | allOf(...filters) | All conditions must match (alias for and) | | anyOf(...filters) | At least one condition must match (alias for or) | | noneOf(...filters) | No conditions must match (negated or) | | oneOf(...filters) | Exactly one condition must match (XOR) | | minus(a, b) | Items matching a but not b (set difference) | | xor(a, b) | Items matching exactly one of a or b (symmetric difference) | | simplify(filter) | Algebraic simplification: dedup, double negation collapse | | eq() / neq() / gt() / gte() / lt() / lte() | Functional filter constructors | | inArray() / notInArray() | Array membership filters | | contains() / startsWith() / endsWith() | String operation filters | | between() / exists() | Range and presence filters | | like() / iLike() | SQL LIKE pattern matching (case-sensitive / insensitive) | | isNull() / isNotNull() | Null/undefined presence checks | | isEmpty() / isNotEmpty() | Empty array/string checks | | fuzzy(data, field, query, opts?) | Fuzzy match with relevance scores | | search(data, query, { fields }) | Multi-field weighted search | | ScoredItem<T> | Result type: { item: T; score: number } | | validateFilter(input, opts?) | Validate untrusted ASTs before translating | | toDrizzleOrderBy(sort) | Sort → Drizzle orderBy objects | | toPrismaOrderBy(sort) | Sort → Prisma orderBy | | toMongoSort(sort) | Sort → MongoDB sort | | toSqlOrderBy(sort) | Sort → SQL ORDER BY clause |

Built-in operators: equals, notEquals, in, notIn, exists, isNull, isNotNull, isEmpty, isNotEmpty, contains, notContains, startsWith, endsWith, matches, like, iLike, greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals, between.

Logical operators

| Export | Purpose | | --- | --- | | allOf(...filters) | All conditions must match (alias for and) | | anyOf(...filters) | At least one condition must match (alias for or) | | noneOf(...filters) | No conditions must match (negated or) | | oneOf(...filters) | Exactly one condition must match (XOR) | | xor(a, b) | Items matching exactly one of a or b | | minus(a, b) | Items matching a but not b (set difference) | | simplify(filter) | Algebraic simplification: dedup, double negation collapse |

Comparison operators

| Export | Purpose | | --- | --- | | eq() / neq() / gt() / gte() / lt() / lte() | Functional filter constructors | | inArray() / notInArray() | Array membership filters | | contains() / startsWith() / endsWith() | String operation filters | | between() / exists() | Range and presence filters | | like() / iLike() | SQL LIKE pattern matching | | isNull() / isNotNull() | Null/undefined presence checks | | isEmpty() / isNotEmpty() | Empty array/string checks |

Advanced features

| Export | Purpose | | --- | --- | | fuzzy(data, field, query, opts?) | Fuzzy match with relevance scores | | search(data, query, { fields }) | Multi-field weighted search | | adaptive(filter, opts?) | Memory filter: excludes previously matched items | | mcda(data, criteria, opts?) | Multi-criteria weighted ranking | | filterDiff(a, b) | Compare two filters: added, removed, changed, unchanged | | createReplay(filter, data, opts?) | Time-travel replay: track filter results as data evolves |

Adapters

| Export | Purpose | | --- | --- | | filterkit/adapters/{drizzle,mongo,prisma,sql} | Query translators (tree-shaken subpaths) |

Client + Server example

// ── Client (React) ──────────────────────────────────────
import { createFilter } from "@azghr/filterkit";

interface User {
  name: string;
  age: number;
  email: string;
  profile: { city: string; };
}

const filter = createFilter<User>()
  .where("age", "greaterThanOrEquals", 18)
  .where("email", "endsWith", "@example.com")
  .or((b) => b.where("profile.city", "equals", "Berlin"))
  .build();

const sort = [{ field: "age", direction: "desc" }];

// Send filter + sort to your API
const res = await fetch("/api/users/search", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ filter, sort }),
});
const users = await res.json();
// ── Server (Express) ────────────────────────────────────
import express from "express";
import { validateFilter } from "@azghr/filterkit";
import { toPrismaWhere, toPrismaOrderBy } from "@azghr/filterkit/adapters/prisma";

const app = express();
app.use(express.json());

app.post("/api/users/search", async (req, res) => {
  const { filter, sort } = req.body;

  const result = validateFilter(filter, {
    fields: ["age", "email", "profile.city"],
    operators: ["equals", "greaterThanOrEquals", "endsWith"],
  });

  if (!result.valid) {
    return res.status(400).json({ error: result.error });
  }

  const users = await prisma.user.findMany({
    where: toPrismaWhere(result.filter),
    orderBy: sort ? toPrismaOrderBy(sort) : undefined,
  });
  res.json(users);
});
// ── Server (Hono + Drizzle) ─────────────────────────────
import { Hono } from "hono";
import { validateFilter } from "@azghr/filterkit";
import { toDrizzleWhere, toDrizzleOrderBy } from "@azghr/filterkit/adapters/drizzle";

const app = new Hono();

app.post("/api/users/search", async (c) => {
  const { filter, sort } = await c.req.json();

  const result = validateFilter(filter, {
    fields: ["age", "email", "profile.city"],
    operators: ["equals", "greaterThanOrEquals", "endsWith"],
  });

  if (!result.valid) {
    return c.json({ error: result.error }, 400);
  }

  const users = await db
    .select()
    .from(usersTable)
    .where(toDrizzleWhere(result.filter, { column: (f) => usersTable[f] }))
    .orderBy(...(sort ? toDrizzleOrderBy(sort, (f) => usersTable[f]) : []));

  return c.json(users);
});

Non-goals

filterkit focuses on filtering and query translation. These features are explicitly out of scope:

  • UI components — Use with React/Vue/Angular components
  • Form handling — Integrate with form libraries separately
  • Authentication/authorization — Handle auth logic separately
  • Data validation — Use validation libraries for input validation
  • Pagination — Compose with pagination libraries

Related Packages

Caching & Concurrency:

  • @azghr/singlet — Deduplicate concurrent async calls
  • staleness — Stale-while-revalidate caching for async functions

Text Processing:

  • @azghr/shorn — Truncate strings by byte budget without breaking graphemes
  • seriatim — Sequential processing utilities

HTTP & Network:

  • forbear — Read server rate-limit instructions from HTTP responses
  • forestall — Delay execution until a condition is met
  • obviate — Render operations unnecessary through caching

System & Process:

  • quiesce — Ordered, timeboxed graceful shutdown for Node
  • sortition — Deterministic percentage rollouts and A/B bucketing
  • stanch — Stop flows or operations based on conditions

Utilities:

  • expunge — Remove or exclude items from collections
  • occlude — Hide or mask data and functionality
  • placemark — Geographic location and mapping utilities
  • specie — Currency and financial calculations

License

MIT