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

@chromav/frizzle

v0.1.2

Published

A fun, type-safe query compiler with a Pratt parser, pluggable code generators, and built-in validation. Filter, sort, and query your data.

Readme

frizzle

A type-safe query compiler and service layer toolkit. Parse URL-safe filter strings, compile to database queries (Drizzle ORM or raw SQL), and compose interceptor pipelines for scoping, soft-delete, and audit trails — with zero exceptions thrown.

?filter=status eq 'active' AND priority eq 'high'&sort=createdAt:desc&limit=50

Install

npm install frizzle

Drizzle ORM is an optional peer dependency — only needed if you use the Drizzle code generators.

npm install drizzle-orm  # if using frizzle/drizzle

Quick Start

Filter a query

import { isSuccess } from "frizzle";
import { compileFilter } from "frizzle/drizzle";

const result = await compileFilter(
  "status eq 'active' AND priority eq 'high'",
  { status: "status", priority: "priority" }
);

if (isSuccess(result)) {
  const tasks = await db.query.tasks.findMany({
    where: result.data,
  });
}

Filter + sort with schema validation

import { isSuccess } from "frizzle";
import { compileFilter, compileSort } from "frizzle/drizzle";
import type { Schema } from "frizzle";

const schema: Schema = {
  status: { type: "enum", enumValues: ["active", "pending", "done"] },
  priority: { type: "enum", enumValues: ["low", "medium", "high"] },
  title: { type: "string" },
  createdAt: { type: "date" },
};

const columns = {
  status: "status",
  priority: "priority",
  title: "title",
  createdAt: "createdAt",
};

// Validates field names, operators, and value types before compiling
const filterResult = await compileFilter(
  "status eq 'active' AND title contains 'deploy'",
  columns,
  { schema }
);

const sortResult = compileSort("createdAt:desc", columns);

if (isSuccess(filterResult) && isSuccess(sortResult)) {
  const tasks = await db.query.tasks.findMany({
    where: filterResult.data,
    orderBy: sortResult.data,
  });
}

SQL builder alternative (Drizzle)

import { isSuccess } from "frizzle";
import { compileFilterToSQL } from "frizzle/drizzle";

// Pass Drizzle column objects instead of strings
const result = compileFilterToSQL("status eq 'active'", {
  status: tasks.status,
  priority: tasks.priority,
});

if (isSuccess(result)) {
  const rows = await db.select().from(tasks).where(result.data);
}

Raw SQL target

For databases Drizzle doesn't cover (e.g., Trino, Spark, Iceberg):

import { compileSelect } from "frizzle/sql";
import { sqliteDialect } from "frizzle/sql";

const columns = { status: "status", priority: "priority", createdAt: "created_at" };

const { sql, parameters } = compileSelect("tasks", {
  filter: { status: "active", priority: { gte: 3 } },
  sort: { createdAt: "desc" },
  take: 20,
}, columns, sqliteDialect);

// sql: SELECT * FROM "tasks" WHERE "status" = ? AND "priority" >= ? ORDER BY "created_at" DESC LIMIT ?
// parameters: ["active", 3, 20]

Interceptor pipelines

Compose cross-cutting concerns as reusable interceptors:

import {
  scopeQueryInterceptor,
  softDeleteQueryInterceptor,
  scopeWriteInterceptor,
  softDeleteWriteInterceptor,
  injectWriteInterceptor,
} from "frizzle";

// Read pipeline: every query is scoped to workspace + excludes deleted
const queryInterceptors = [
  scopeQueryInterceptor({ workspaceId: "ws-123" }),
  softDeleteQueryInterceptor(),
];

// Write pipeline: scope + audit + soft-delete transform
const writeInterceptors = [
  scopeWriteInterceptor({ workspaceId: "ws-123" }),
  injectWriteInterceptor("user-456"),
  softDeleteWriteInterceptor("user-456"),
];

Query Syntax

Filter operators

| Operator | Category | Example | |----------|----------|---------| | eq, ne | Comparison | status eq 'active' | | gt, gte, lt, lte | Comparison | age gte 18 | | like, ilike | String | name like 'john' | | contains, starts_with, ends_with | String | title contains 'deploy' | | in, not_in | Array | status in ['active', 'pending'] | | between | Range | age between [18, 65] | | is_null, is_not_null | Null check | assignedTo is_null |

Symbol aliases work too: =, !=, >, >=, <, <=.

Logical operators

status eq 'active' AND priority eq 'high'
status eq 'active' OR status eq 'pending'
(status eq 'active' OR status eq 'pending') AND priority eq 'high'

AND binds tighter than OR. Parentheses override precedence.

Sorting and pagination

sort=name:asc,createdAt:desc
limit=50
offset=100

Error Handling

All operations return Result<T> — no exceptions thrown.

import { isSuccess, isError } from "frizzle";

const result = await compileFilter("bad ?? syntax", columns);

if (isError(result)) {
  result.error.message;     // "Unexpected token '??' at position 4"
  result.error.code;        // "SYNTAX_ERROR"
  result.error.suggestions; // Possible fixes
}

Entry Points

| Import | Contents | |--------|----------| | frizzle | Core: compiler, parser, AST types, result types, schema types, query string parsing, descriptors, interceptors | | frizzle/drizzle | Drizzle ORM: compileFilter, compileFilterToSQL, compileSort, DrizzleCodeGenerator | | frizzle/sql | Raw SQL: compileSelect, compileFilter, compileFilterNode, compileInsert, compileUpdate, compileDelete, Dialect, sqliteDialect |

The core package has zero dependencies. frizzle/drizzle requires drizzle-orm as a peer dependency. frizzle/sql has zero dependencies.

Descriptors

Frizzle uses database-agnostic intermediate representations that flow through interceptor pipelines before reaching a database adapter.

QueryDescriptor — describes a read operation:

import { createQueryDescriptor, mergeQueryDescriptor } from "frizzle";

const descriptor = mergeQueryDescriptor(createQueryDescriptor(), {
  filter: { status: "active", workspaceId: "ws-1" },
  sort: { createdAt: "desc" },
  take: 20,
  skip: 0,
  select: { id: true, title: true, status: true },
  include: { assignee: true },
});

WriteDescriptor — describes a write operation:

import { createWriteDescriptor, mergeWriteDescriptor } from "frizzle";

// Insert
const insert = createWriteDescriptor("insert", { title: "New task", status: "backlog" });

// Update
const update = mergeWriteDescriptor(
  createWriteDescriptor("update", { status: "done" }),
  { targetIds: ["task-1", "task-2"] },
);

// Delete
const remove = mergeWriteDescriptor(
  createWriteDescriptor("delete"),
  { targetIds: ["task-3"] },
);

Reference Interceptors

Reusable implementations for common service layer patterns. All exported from the main frizzle entry point.

Query interceptors

| Interceptor | Description | |------------|-------------| | scopeQueryInterceptor(fields) | Appends tenant/workspace filter to every query | | softDeleteQueryInterceptor(config?) | Excludes soft-deleted rows (isDeleted: false) | | defaultIncludesInterceptor(defaults) | Deep-merges default relation includes | | extraScopeInterceptor(scopeFn) | Adds entity-specific filter conditions from a factory |

Write interceptors

| Interceptor | Description | |------------|-------------| | scopeWriteInterceptor(fields) | Injects scope values into every write | | softDeleteWriteInterceptor(userId, config?) | Transforms delete → update with isDeleted: true | | injectWriteInterceptor(userId, config?) | Injects createdById on insert, updatedById on update | | extraScopeWriteInterceptor(scopeFn) | Injects additional scope fields on insert |

All interceptors are generalized (configurable column names, generic scope fields) and use only frizzle's own types — zero external dependencies.

SQL Target

The frizzle/sql entry point compiles descriptors to parameterized SQL strings. It supports any SQL database through the Dialect interface.

Dialect interface

import type { Dialect } from "frizzle/sql";

const trinoDialect: Dialect = {
  name: "trino",
  quoteIdentifier: (name) => `"${name}"`,
  parameter: (index) => `$${index + 1}`,
  booleanLiteral: (v) => v ? "TRUE" : "FALSE",
  supportsILike: true,
  escapeLikePattern: (p) => p.replace(/[%_\\]/g, "\\$&"),
};

Write operations

import { compileInsert, compileUpdate, compileDelete } from "frizzle/sql";
import { createWriteDescriptor, mergeWriteDescriptor } from "frizzle";
import { sqliteDialect } from "frizzle/sql";

const insert = createWriteDescriptor("insert", { title: "Task", status: "backlog" });
const { sql, parameters } = compileInsert("tasks", insert, sqliteDialect);
// sql: INSERT INTO "tasks" ("title", "status") VALUES (?, ?)

const update = mergeWriteDescriptor(
  createWriteDescriptor("update", { status: "done" }),
  { targetIds: ["t-1"] },
);
const { sql, parameters } = compileUpdate("tasks", update, sqliteDialect);
// sql: UPDATE "tasks" SET "status" = ? WHERE "id" = ?

Architecture

URL query string
    ↓ parseQueryString()
QueryDescriptor (database-agnostic IR)
    ↓ interceptor pipeline (scope, soft-delete, inject, ...)
QueryDescriptor (enriched)
    ↓ adapter
    ├── frizzle/drizzle → Drizzle RQB findMany/findFirst
    └── frizzle/sql     → parameterized SQL string

Documentation

Core

Database targets

Guides

  • Recipes — Express, Hono, Next.js, React Router examples + filter UI patterns
  • URL Examples — 50+ real-world URL query patterns

License

MIT