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

@routier/mongodb-plugin

v0.4.0

Published

MongoDB query language generation for routier. Translates the core expression tree into an MQL filter document.

Readme

@routier/mongodb-plugin

MQL generation for Routier. toMql turns a core expression tree into a MongoDB filter document.

This package is the translator only. It does not implement IDbPlugin, open a connection, or write anything. It is the MongoDB counterpart of toSql in @routier/sql-plugin-core.

import { toMql } from '@routier/mongodb-plugin';

toMql(expression);
// { $and: [ { name: { $eq: 'Ada' } }, { price: { $gt: 10 } } ] }

What it emits

| Filter | MQL | | --- | --- | | x.name === 'Ada' | { name: { $eq: 'Ada' } } | | x.price > 10 | { price: { $gt: 10 } } | | !(x.price > 10) | { price: { $lte: 10 } } | | 10 > x.price | { price: { $lt: 10 } } | | x.deletedAt == null | { deletedAt: { $eq: null } } | | x.name.startsWith('Ad') | { name: { $regex: '^Ad' } } | | !x.name.startsWith('Ad') | { name: { $not: /^Ad/ } } | | x.label === 'hello' (renamed wire_label) | { wire_label: { $eq: 'hello' } } | | x.payload.inner.value === 'deep' | { 'payload.inner.value': { $eq: 'deep' } } | | x.tags.includes('x') (array property) | { tags: 'x' } | | ['a','b'].includes(x.status) | { status: { $in: ['a','b'] } } | | x.tags.length === 2 | { $expr: { $eq: [{ $size: '$tags' }, { $literal: 2 }] } } | | x.price > x.cost | { $expr: { $gt: ['$price', '$cost'] } } | | x => true | {} |

Four things worth knowing

Operand order changes the operator. MQL has no { 5: { $lt: '$price' } } — a field has to be a key. So 10 > x.price becomes { price: { $lt: 10 } }, with the comparator mirrored. A translator that ignores which side the property is on emits a perfectly valid query for the opposite range, which is the MQL form of the operand-order defect recorded against the SQL equals path.

Negation names the inverse operator rather than wrapping in $not. $not on a field predicate also matches documents where the field is missing, so $not: { $gt: 5 } and $lte: 5 are different queries. Negated comparisons use the inverse operator, which keeps them over present values — what !(x > 5) means to the caller. String patterns are the exception: a negated pattern has no inverse operator and does use $not, over a RegExp instance, because $not rejects a $regex string.

Nested properties use dot notation. The SQL plugins store a nested object as a JSON column and route filters into it to memory. Mongo addresses payload.inner.value directly, so nested filtering pushes down here when it cannot elsewhere. Each path segment resolves through from, so a renamed nested property addresses the stored name.

A transformed property switches to $expr. LOWER(col) = ? has no plain-filter equivalent, so those comparisons become aggregation expressions ($toLower, $toUpper, $strLenCP, $size, $regexMatch). $expr cannot use an index the way a field predicate can, so it is reached for only where a plain predicate cannot express the comparison.

Null

Mongo draws a distinction the SQL engines do not: { f: null } matches documents where f is null and documents where f is absent, whereas col IS NULL has no absent case because a column always exists.

The two agree for documents Routier wrote — a schema serialises a nullable property as an explicit null rather than omitting it. They diverge over documents written by something else. This translator takes the Mongo-native reading rather than adding a $type check that would make Routier's own rows behave differently from every other backend.

When it throws

  • A comparator with no MQL form.
  • A string pattern against a null operand — LIKE '%null%' is never what the caller meant.
  • A not-parsable expression. Core produces this for a filter it has no rule for, so it arrives in normal use. The error names the fix: evaluate the filter in memory by routing the query option to the memory execution target. Falling back silently would turn a bounded query into a full collection scan without saying so.

Verification

src/mql.test.ts covers the mapping two ways: against hand-built expression trees, and against trees the real parser produced from filters a caller would write.

Shape assertions prove what the translator emits, not what an engine does with it — the lesson e2e/src/dialectConformance.ts records about the SQL builder. So the output was also executed against MongoDB 7 over a seeded collection, asserting the matched _ids for 27 filters covering every row in the table above. $not over a RegExp, $size, $toLower and $regexMatch were each confirmed against the server rather than assumed.

One known gap, in core rather than here: the parser returns not-parsable for x.name.toLowerCase() === 'ada', though it parses x.name.toLowerCase().startsWith('ad') and x.tags.length === 2 normally. The $expr equality path is reachable through length; it is the case-transform equality that never reaches this translator.