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

mikro-orm-filter-query-schema

v1.2.0

Published

A Zod-based schema builder for validating MikroORM filter queries.

Readme

mikro-orm-filter-query-schema

A Zod-based schema builder for validating MikroORM filter queries with configurable security limits.

Installation

npm install mikro-orm-filter-query-schema
# or
pnpm add mikro-orm-filter-query-schema
# or
yarn add mikro-orm-filter-query-schema

Features

  • Build type-safe Zod schemas for MikroORM FilterQuery
  • Configurable security limits to prevent DoS attacks
  • Support for all MikroORM comparison operators
  • Field whitelist validation
  • Nested logical operators ($and, $or, $not)
  • Field name replacement (string path or callback function)

Usage

Basic Usage

import { FilterQuerySchemaBuilder } from "mikro-orm-filter-query-schema";

interface User {
  id: number;
  name: string;
  age: number;
  isActive: boolean;
  roles: string[];
}

const schema = new FilterQuerySchemaBuilder<User>()
  .addField({ field: "id", type: "number" })
  .addField({ field: "name", type: "string" })
  .addField({ field: "age", type: "number" })
  .addField({ field: "isActive", type: "boolean" })
  .addField({ field: "roles", type: "string", array: true })
  .build();

// Validate filter queries
const result = schema.safeParse({
  name: "John",
  age: { $gte: 18 },
});

if (result.success) {
  // Use result.data as FilterQuery<User>
}

With Custom Limits

const schema = new FilterQuerySchemaBuilder<User>({
  maxDepth: 3,
  maxConditions: 10,
  maxOrBranches: 3,
  maxArrayLength: 50,
})
  .addField({ field: "id", type: "number" })
  .addField({ field: "name", type: "string" })
  .build();

Field Replacement

You can use the replacement option to transform field names in the output query.

String Path Replacement

Map a flat field name to a nested property path:

interface Post {
  id: number;
  title: string;
  author: {
    name: string;
    age: number;
  };
}

const schema = new FilterQuerySchemaBuilder<Post>()
  .addField({ field: "id", type: "number" })
  .addField({ field: "title", type: "string" })
  .addField({ field: "authorName", type: "string", replacement: "author.name" })
  .addField({ field: "authorAge", type: "number", replacement: "author.age" })
  .build();

// Input: { authorName: "John", authorAge: { $gte: 18 } }
// Output: { author: { name: "John", age: { $gte: 18 } } }

Callback Replacement

Use a callback function for custom query transformation:

interface Article {
  id: number;
  title: string;
  content: string;
}

const schema = new FilterQuerySchemaBuilder<Article>()
  .addField({ field: "id", type: "number" })
  .addField({
    field: "keyword",
    type: "string",
    replacement: ({ operator, value }) => ({
      $or: [
        { title: { [operator]: value } },
        { content: { [operator]: value } },
      ],
    }),
  })
  .build();

// Input: { keyword: "search" }
// Output: { $or: [{ title: "search" }, { content: "search" }] }

// Input: { keyword: { $ne: "excluded" } }
// Output: { $or: [{ title: { $ne: "excluded" } }, { content: { $ne: "excluded" } }] }

The callback receives an object with:

  • field: The original field name
  • operator: The operator being used ($eq, $ne, $in, $fulltext, etc.)
  • value: The value associated with the operator

Supported Operators

Equality Operators (all types)

| Operator | Description | Example | |----------|-------------|---------| | $eq | Equal | { age: { $eq: 25 } } | | $ne | Not equal | { age: { $ne: 25 } } | | $in | In array | { id: { $in: [1, 2, 3] } } | | $nin | Not in array | { id: { $nin: [1, 2, 3] } } |

Comparison Operators (number, date only)

| Operator | Description | Example | |----------|-------------|---------| | $gt | Greater than | { age: { $gt: 18 } } | | $gte | Greater than or equal | { age: { $gte: 18 } } | | $lt | Less than | { age: { $lt: 65 } } | | $lte | Less than or equal | { age: { $lte: 65 } } |

Array Operators

| Operator | Description | Example | |----------|-------------|---------| | $contains | Array contains all | { roles: { $contains: ["admin"] } } | | $overlap | Array overlaps | { roles: { $overlap: ["admin", "user"] } } |

Logical Operators

| Operator | Description | Example | |----------|-------------|---------| | $and | Logical AND | { $and: [{ age: { $gte: 18 } }, { isActive: true }] } | | $or | Logical OR | { $or: [{ name: "John" }, { name: "Jane" }] } | | $not | Logical NOT | { $not: { isActive: false } } |

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | maxDepth | number | 5 | Maximum nesting depth for filter queries | | maxConditions | number | 20 | Maximum number of field conditions in a filter | | maxOrBranches | number | 5 | Maximum number of branches in $or operator | | maxArrayLength | number | 100 | Maximum array length for $in/$nin/$contains/$overlap |

Field Options

interface FieldOptions {
  field: string;           // Field name
  type: "string" | "number" | "boolean" | "date";
  array?: boolean;         // Is array field (enables $contains, $overlap)
  fulltext?: boolean;      // Enable $fulltext operator (string fields only)
  prefix?: boolean;        // Enable $prefix operator (string fields only)
  replacement?: string | ((args: ReplacementCallbackArgs) => FilterQuery);
}

Fulltext Search

When a string field has fulltext: true, the $fulltext operator becomes available for that field:

const schema = new FilterQuerySchemaBuilder<Article>()
  .addField({ field: "title", type: "string", fulltext: true })
  .addField({ field: "content", type: "string" }) // no fulltext
  .build();

// $fulltext is allowed for title
schema.parse({ title: { $fulltext: "search term" } });
// Output: { title: { $fulltext: "search term" } }

// $fulltext is NOT allowed for content (will fail validation)
schema.safeParse({ content: { $fulltext: "search" } }).success; // false

// Other operators still work normally
schema.parse({ title: { $eq: "exact match" } });
// Output: { title: { $eq: "exact match" } }

Fulltext Search Operator

| Operator | Description | Example | |----------|-------------|---------| | $fulltext | Full-text search | { title: { $fulltext: "search term" } } |

Note: The $fulltext operator is only available for string fields with fulltext: true option.

Prefix Search

When a string field has prefix: true, the $prefix operator becomes available for that field. The $prefix operator is converted to $like with the value appended with %, and special characters (%, _, \) are automatically escaped:

const schema = new FilterQuerySchemaBuilder<Article>()
  .addField({ field: "title", type: "string", prefix: true })
  .addField({ field: "content", type: "string" }) // no prefix
  .build();

// $prefix is allowed for title
schema.parse({ title: { $prefix: "Hello" } });
// Output: { title: { $like: "Hello%" } }

// Special characters are escaped
schema.parse({ title: { $prefix: "100%" } });
// Output: { title: { $like: "100\\%%" } }

// $prefix is NOT allowed for content (will fail validation)
schema.safeParse({ content: { $prefix: "Hello" } }).success; // false

Prefix Search Operator

| Operator | Description | Example | |----------|-------------|---------| | $prefix | Prefix search (converted to $like) | { title: { $prefix: "Hello" } }{ title: { $like: "Hello%" } } |

Note: The $prefix operator is only available for string fields with prefix: true option.

Security

This library provides configurable limits to protect against malicious queries:

  • Depth limiting: Prevents deeply nested queries that could cause stack overflow
  • Condition limiting: Limits the number of conditions to prevent complex queries
  • Or branch limiting: Limits $or branches to prevent exponential query complexity
  • Array length limiting: Limits array sizes in $in, $nin, $contains, $overlap
  • Field whitelist: Only allows explicitly registered fields

License

MIT