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

biome-drizzle-best-practices-plugin

v2.0.0

Published

Biome plugin that enforces Drizzle ORM best practices — join conditions, non-empty .where(), and safe sql.raw usage.

Readme

biome-drizzle-best-practices-plugin

A Biome plugin (written in GritQL) that enforces Drizzle ORM best practices — catching the query-chain mistakes that quietly turn into cartesian products or SQL injection at runtime.

It adds three checks that Biome does not ship itself: enforce-join-with-condition, no-empty-where, and no-sql-raw-interpolation.

Missing delete/update guards? The .delete() / .update().set()-without-.where() checks that earlier versions of this plugin provided are now built into Biome's drizzle domain as noDrizzleDeleteWithoutWhere and noDrizzleUpdateWithoutWhere. Enable them there instead — see Delete/update without .where() below.

// flagged
db.select().from(users).leftJoin(posts);       // cartesian product — no `on`
query.where();                                 // empty filter matches all rows
sql.raw(`SELECT * FROM users WHERE id = ${id}`); // SQL injection

// safe
db.select().from(users).leftJoin(posts, eq(users.id, posts.userId));
query.where(eq(users.id, id));
sql`SELECT * FROM users WHERE id = ${id}`;

Rules

| Rule | Flags | Why | | --- | --- | --- | | drizzle/enforce-join-with-condition | .leftJoin(t) / .rightJoin(t) / .innerJoin(t) / .fullJoin(t) called with only the table | A join with no on predicate produces a cartesian product. | | drizzle/no-empty-where | .where() with no argument | An empty filter silently matches every row. | | drizzle/no-sql-raw-interpolation | sql.raw(${value}) | sql.raw bypasses parameterisation, so an interpolated value is spliced straight into the SQL string. |

All rules report a diagnostic only (severity error, category plugin); none apply an auto-fix, because the correct repair is query-specific — the plugin flags the hazard and leaves the fix to you.

Delete/update without .where()

A bare .delete() removes every row in the table, and a bare .update(t).set(v) rewrites every row. These hazards are no longer checked by this plugin — Biome now ships them in its built-in drizzle domain:

  • noDrizzleDeleteWithoutWhere — flags .delete() with no .where().
  • noDrizzleUpdateWithoutWhere — flags .update().set() with no .where().

The domain activates automatically once drizzle-orm >=0.9.0 is a dependency, but both rules are nursery, so enabling the domain as "recommended" turns nothing on. Enable them explicitly:

{
  "linter": {
    "domains": {
      "drizzle": "all"          // or turn each rule on individually
    }
  }
}

Unlike this plugin's structural matching, the built-in rules are type-aware, so they only fire on real Drizzle queries — no need to scope them to Drizzle files by hand.

enforce-join-with-condition

// flagged — no `on` condition, yields a cartesian product
db.select().from(users).leftJoin(posts);

// safe
db.select().from(users).leftJoin(posts, eq(users.id, posts.userId));

Matches the four join methods whose second argument is the on predicate (leftJoin, rightJoin, innerJoin, fullJoin) when called with exactly one argument. The correct two-argument form is left untouched.

no-empty-where

db.select().from(users).where();   // flagged
db.update(users).set({ x: 1 }).where(); // flagged
db.select().from(users).where(eq(users.id, 1)); // safe

no-sql-raw-interpolation

sql.raw(`SELECT * FROM users WHERE id = ${userId}`); // flagged
sql.raw("SELECT * FROM users");                      // safe — constant string
sql.raw(`SELECT * FROM users`);                      // safe — no interpolation
sql`SELECT * FROM users WHERE id = ${userId}`;       // safe — parameterised tagged template

Only sql.raw(...) whose argument is a template literal with an interpolation is flagged. A plain string constant (including a template literal with no ${}) is left alone, and the tagged sql template escapes its interpolations safely, so it is never flagged.

Limitations

The plugin matches structure, not types, so it keys off method names. If you have an unrelated object with a .where() method (or a .leftJoin / sql.raw call), it will also be flagged. eslint-plugin-drizzle avoids this with a drizzleObjectName option, but Biome's GritQL plugins cannot yet take configuration — so the match is intentionally broad. Scope the plugin with Biome's includes/overrides to the files that use Drizzle if false positives are a problem. (Biome's built-in drizzle domain rules are type-aware and don't have this caveat.)

Usage

Install the plugin as a dev dependency:

npm install -D biome-drizzle-best-practices-plugin

Reference it from your Biome configuration:

{
  "plugins": ["biome-drizzle-best-practices-plugin/drizzle.grit"],
  "linter": {
    "rules": { "recommended": true }
  }
}

Then run the linter:

npx @biomejs/biome lint <files>

Requires Biome 2.0+ (GritQL plugins landed in v2.0). Developed and tested against Biome 2.5.

Using it directly from this repo instead? Set "plugins": ["./drizzle.grit"] and point the path at the checked-out file.

Try it

npm install
npx @biomejs/biome lint example.ts

Tests

Snapshot tests live in tests/. Each case is a pair: tests/fixtures/<name>.ts (the source to lint) and <name>.expected.json (the diagnostics it should produce, as an order-independent array of { "line": <number>, "rule": "<slug>" }). The runner (scripts/run-tests.mjs) runs biome lint --reporter=json on each fixture with only the plugin enabled and compares the extracted diagnostics against the expectation.

npm test

Covered cases include each rule's flagged form and its safe counterpart: all four join methods without a condition vs. the two-argument form, empty vs. populated .where(), and sql.raw with interpolation vs. constant strings vs. the safe tagged sql template.

How it works

The plugin is one Biome GritQL file, drizzle.grit. The empty-where call is matched with a code-snippet pattern (`$obj.where()`). The join rule matches a JsCallExpression whose member name is one of the four join methods and whose argument list has exactly one element ($args <: [$table]). The sql.raw rule matches when the argument is a JsTemplateExpression containing a JsTemplateElement (an interpolation), so constant strings are left alone.

Releasing

Versions and the changelog are managed with Changesets.

  1. Add a changeset describing a change: npx changeset.
  2. Commit the changeset to your branch.
  3. On merge to main, the Release workflow opens a "Version Packages" pull request that bumps the version and updates CHANGELOG.md.
  4. Merge that PR and the workflow publishes the new version to npm.

The workflow needs an NPM_TOKEN secret in the repo. CI runs the test suite on every push and pull request (.github/workflows/ci.yml).


Inspired by biome-plugin-drizzle and eslint-plugin-drizzle from the Drizzle Team.