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

@mohasinac/sievejs

v1.0.0

Published

Database-level filtering, sorting, and pagination middleware inspired by Sieve

Readme

SieveJS

npm version npm downloads CI Status License

SieveJS is a JavaScript-first, ESM-only filtering, sorting, and pagination library inspired by Sieve for .NET.

It applies query logic at the database/query-builder layer (Knex/Mongoose/Prisma/SQL Server/Firebase/Couchbase), instead of in-memory filtering by default.

Install

npm install @mohasinac/sievejs

📦 View on npm

Quick start (Express)

import express from "express";
import {
    createKnexAdapter,
    createSieveMiddleware,
    SieveProcessorBase,
} from "@mohasinac/sievejs";

const processor = new SieveProcessorBase({
    adapter: createKnexAdapter(),
    autoLoadConfig: true, // reads sieve.config.json / .js / .mjs / .cjs
});

const app = express();

app.get(
    "/posts",
    createSieveMiddleware({
        processor,
        queryFactory: (req) => req.db("posts"),
    }),
    async (req, res, next) => {
        try {
            const rows = await req.sieveQuery;
            res.json(rows);
        } catch (error) {
            next(error);
        }
    },
);

Versioning

  • Package: @mohasinac/sievejs
  • Current version: 1.0.0
  • Version policy: SemVer
    • MAJOR: breaking API changes
    • MINOR: backward-compatible features
    • PATCH: backward-compatible fixes
  • Note: while in 0.x, minor versions may include changes that would otherwise be major in 1.x.

Features

  • Sieve DSL parsing (filters, sorts, page, pageSize)
  • Adapter-based query translation (Knex, Mongoose, Prisma, SQL Server, Firebase, Couchbase)
  • Metadata/attribute-style mapping (Sieve, SieveAttribute, buildFieldsFromClass)
  • Typed exceptions (SieveException, SieveMethodNotFoundException, SieveIncompatibleMethodException)
  • Integration pattern for Express, Next, and custom frameworks
  • Pipe helper for non-middleware pipelines

Usage (Express)

In this example, we use posts to show filtering/sorting/pagination.

1. Create and configure a processor

import { createKnexAdapter, SieveProcessorBase } from "@mohasinac/sievejs";

const processor = new SieveProcessorBase({
    adapter: createKnexAdapter(),
    autoLoadConfig: true, // reads sieve.config.json / .js / .mjs / .cjs
});

2. Define which fields are filterable/sortable

Option A: Config file (sieve.config.json)

{
    "options": {
        "caseSensitive": false,
        "defaultPageSize": 20,
        "maxPageSize": 100,
        "throwExceptions": true
    },
    "fields": {
        "title": { "canFilter": true, "canSort": true },
        "created": {
            "path": "created_at",
            "canFilter": true,
            "canSort": true
        }
    }
}

Option B: Attributes/metadata in code

import { Sieve, buildFieldsFromClass } from "@mohasinac/sievejs";

class Post {}

Sieve({ canFilter: true, canSort: true })(Post.prototype, "title");
Sieve({ canFilter: true, canSort: true, name: "created" })(
    Post.prototype,
    "createdAt",
);

const fields = buildFieldsFromClass(Post);

In plain JavaScript, decorators are applied as function calls. In TypeScript/Babel you can use decorator syntax if enabled.

3. Apply Sieve in your route

import express from "express";
import {
    createSieveMiddleware,
    createKnexAdapter,
    SieveProcessorBase,
} from "@mohasinac/sievejs";

const app = express();

const processor = new SieveProcessorBase({
    adapter: createKnexAdapter(),
    autoLoadConfig: true,
});

app.get(
    "/posts",
    createSieveMiddleware({
        processor,
        queryFactory: (req) => req.db("posts"),
    }),
    async (req, res, next) => {
        try {
            const rows = await req.sieveQuery;
            res.json(rows);
        } catch (error) {
            next(error);
        }
    },
);

4. Send a request

GET /posts?sorts=likeCount,commentCount,-created&filters=likeCount>10,title@=awesome&page=1&pageSize=10

Add custom sort/filter methods

Custom methods can be provided via processor configuration:

import { createSieveProcessor, createPrismaAdapter } from "@mohasinac/sievejs";

const processor = createSieveProcessor({
    adapter: createPrismaAdapter(),
    fields: {
        title: { canFilter: true, canSort: true },
        created: { canFilter: true, canSort: true },
    },
    customFilters: {
        isNew(query, operator, values) {
            return {
                ...query,
                where: {
                    ...(query.where ?? {}),
                    createdAt: { gte: new Date(values[0]) },
                },
            };
        },
    },
    customSorts: {
        popularity(query, useThenBy, descending) {
            const orderBy = Array.isArray(query.orderBy) ? query.orderBy : [];
            const dir = descending ? "desc" : "asc";
            return {
                ...query,
                orderBy: [
                    ...orderBy,
                    { likeCount: dir },
                    { commentCount: dir },
                ],
            };
        },
    },
});

Configure Sieve

Processor options

  • caseSensitive: property-name case sensitivity
  • defaultPageSize: default page size when none provided
  • maxPageSize: upper bound for requested page size
  • throwExceptions: throw typed errors instead of silent fallback
  • ignoreNullsOnNotEqual: null behavior for !=

Configure via constructor

const processor = new SieveProcessorBase({
    autoLoadConfig: false,
    adapter: createKnexAdapter(),
    options: {
        caseSensitive: false,
        defaultPageSize: 20,
        maxPageSize: 100,
        throwExceptions: true,
        ignoreNullsOnNotEqual: true,
    },
    fields: {
        title: { canFilter: true, canSort: true },
        created: { path: "created_at", canFilter: true, canSort: true },
    },
});

Send a request (DSL reference)

Query parameters

  • sorts: comma-delimited ordered list of fields (- prefix means descending)
  • filters: comma-delimited list of {Name}{Operator}{Value}
  • page: page number
  • pageSize: page size

OR logic

  • OR fields: (likeCount|commentCount)>10
  • OR values: title@=new|hot

Escaping

  • Escaped comma: title@=some\,title
  • Escaped pipe: title@=some\|title
  • Literal null string: title==\null

Operators

| Operator | Meaning | | -------- | ------------------------------------ | | == | Equals | | != | Not equals | | > | Greater than | | < | Less than | | >= | Greater than or equal | | <= | Less than or equal | | @= | Contains | | _= | Starts with | | _-= | Ends with | | !@= | Does not contain | | !_= | Does not start with | | !_-= | Does not end with | | @=* | Case-insensitive contains | | _=* | Case-insensitive starts with | | _-=* | Case-insensitive ends with | | ==* | Case-insensitive equals | | !=* | Case-insensitive not equals | | !@=* | Case-insensitive does not contain | | !_=* | Case-insensitive does not start with | | !_-=* | Case-insensitive does not end with |

Nested objects/paths

Use mapped paths for nested fields. Example:

const processor = new SieveProcessorBase({
    adapter: createPrismaAdapter(),
    fields: {
        creatorName: { path: "creator.name", canFilter: true, canSort: true },
    },
});

Then query with:

GET /posts?filters=creatorName==mohasin

Creating your own DSL/model binding

You can map any request shape to Sieve model format (filters, sorts, page, pageSize) in integrations:

import { createSieveIntegration } from "@mohasinac/sievejs";

const runSieve = createSieveIntegration({
    processor,
    requestModel: ({ request }) => ({
        filters: request.body?.where,
        sorts: request.body?.order,
        page: request.body?.p,
        pageSize: request.body?.size,
    }),
    queryFactory: ({ request }) => request.db("posts"),
    execute: ({ query }) => query,
});

Handle Sieve exceptions

With throwExceptions: true, typed errors include:

  • SieveMethodNotFoundException
  • SieveIncompatibleMethodException
  • SieveException (wrapper for unexpected runtime errors)

Express error middleware example:

import {
    SieveException,
    SieveIncompatibleMethodException,
    SieveMethodNotFoundException,
} from "@mohasinac/sievejs";

app.use((error, req, res, next) => {
    if (
        error instanceof SieveMethodNotFoundException ||
        error instanceof SieveIncompatibleMethodException ||
        error instanceof SieveException
    ) {
        return res.status(400).json({
            type: error.name,
            message: error.message,
        });
    }

    next(error);
});

Integrations

Express helper

import { createExpressSieveMiddleware } from "@mohasinac/sievejs/integrations";

Next App Router helper

import { createNextRouteHandler } from "@mohasinac/sievejs/integrations";

Adapters

Prisma

const processor = new SieveProcessorBase({
    adapter: createPrismaAdapter(),
    fields: {
        title: { canFilter: true, canSort: true },
    },
});

const args = processor.apply(req.query, {});
const rows = await prisma.post.findMany(args);

SQL Server

const plan = processor.apply(req.query, {
    whereClauses: [],
    orderByClauses: [],
    params: [],
});
const { text, params } = buildSqlServerQuery(plan, {
    table: "dbo.Posts",
    columns: "*",
});

Firebase

const processor = new SieveProcessorBase({
    adapter: createFirebaseAdapter(),
    fields: {
        tags: { canFilter: true, canSort: false },
        created: { canFilter: true, canSort: true },
    },
});

const query = processor.apply(req.query, db.collection("posts"));
const snapshot = await query.get();

Firebase limitations:

  • contains maps to array-contains
  • startsWith uses range query (>= value, <= value + "\uf8ff")
  • endsWith, case-insensitive operators, and negated string operators are not supported by Firestore queries

Couchbase

const plan = processor.apply(req.query, {
    whereClauses: [],
    orderByClauses: [],
    parameters: {},
    parameterIndex: 0,
});

const { statement, parameters } = buildCouchbaseQuery(plan, {
    bucket: "blog",
    scope: "inventory",
    collection: "posts",
});

Pipes

import { createSievePipe } from "@mohasinac/sievejs";

const sievePipe = createSievePipe({ processor });
const query = sievePipe(req.query, db("posts"));

Module API cheat sheet

Use direct imports when you only need specific modules:

  • Core: import { createSieveProcessor, createSieveMiddleware, createSievePipe } from "@mohasinac/sievejs"
  • Attributes + metadata: import { Sieve, SieveAttribute, buildFieldsFromClass, getSieveMetadata } from "@mohasinac/sievejs/attributes"
  • Exceptions: import { SieveException } from "@mohasinac/sievejs/exceptions"
  • Extensions: import { getMethodExt } from "@mohasinac/sievejs/extensions"
  • Models: import { SieveModel } from "@mohasinac/sievejs/models"
  • Services: import { SieveProcessorBase } from "@mohasinac/sievejs/services"
  • Pipes: import { createSievePipe } from "@mohasinac/sievejs/pipes"
  • Integrations: import { createExpressSieveMiddleware, createNextRouteHandler } from "@mohasinac/sievejs/integrations"
  • Adapters:
    • import { createKnexAdapter } from "@mohasinac/sievejs/adapters/knex"
    • import { createMongooseAdapter } from "@mohasinac/sievejs/adapters/mongoose"
    • import { createPrismaAdapter } from "@mohasinac/sievejs/adapters/prisma"
    • import { createSqlServerAdapter, buildSqlServerQuery } from "@mohasinac/sievejs/adapters/sqlserver"
    • import { createFirebaseAdapter } from "@mohasinac/sievejs/adapters/firebase"
    • import { createCouchbaseAdapter, buildCouchbaseQuery } from "@mohasinac/sievejs/adapters/couchbase"

Upgrading

Migrating from .NET Sieve to SieveJS

  • Replace attribute annotations with JS metadata (Sieve(...)) or fields config.
  • Replace IQueryable pipeline with adapter-native query objects (Knex, Prisma args, etc.).
  • Replace ASP.NET model binding with integration requestModel mapping.
  • Keep filtering/sorting/pagination at query-builder/database layer.

Updating within SieveJS

  • Follow SemVer tags/releases.
  • Read release notes before upgrading minor versions while in 0.x.
  • Re-run npm test and npm pack --dry-run after upgrades.

Included samples

See sample/ for working examples:

  • sample/server.js (Express + createExpressSieveMiddleware)
  • sample/next-route.example.js (Next Route Handler + createNextRouteHandler + Firebase adapter)
  • sample/advanced-modules.example.js (metadata, attributes, extensions, exceptions, models, pipes, services)
  • sample/sieve.config.json

License & Contributing

  • License: MIT (LICENSE)
  • Contributions are welcome via issues and pull requests.