You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@filtron/sql

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@filtron/sql - npm Package Compare versions

Comparing version
1.1.0
to
1.2.0
+2
-2
dist/index.js

@@ -1,4 +0,4 @@

function F(q,E={}){let R={params:[],parameterStyle:E.parameterStyle??"numbered",fieldMapper:E.fieldMapper??((H)=>H),valueMapper:E.valueMapper??((H)=>H),paramIndex:E.startIndex??1};return{sql:$(q,R),params:R.params}}function $(q,E){switch(q.type){case"or":return T(q,E);case"and":return b(q,E);case"not":return z(q,E);case"comparison":return A(q,E);case"oneOf":return Q(q,E);case"notOneOf":return U(q,E);case"exists":return C(q,E);case"booleanField":return G(q,E);case"range":return J(q,E);default:let R=q;throw Error(`Unknown node type: ${q.type}`)}}function T(q,E){let R=$(q.left,E),S=$(q.right,E);return`(${R} OR ${S})`}function b(q,E){let R=$(q.left,E),S=$(q.right,E);return`(${R} AND ${S})`}function z(q,E){return`NOT (${$(q.expression,E)})`}function A(q,E){let R=E.fieldMapper(q.field),S=M(q.operator),H=D(q.value);if(q.operator==="~")H=E.valueMapper(H);let W=j(H,E);return`${R} ${S} ${W}`}function Q(q,E){let R=E.fieldMapper(q.field),S=q.values.map((W)=>D(W));if(S.length===0)return"1 = 0";let H=S.map((W)=>j(W,E));return`${R} IN (${H.join(", ")})`}function U(q,E){let R=E.fieldMapper(q.field),S=q.values.map((W)=>D(W));if(S.length===0)return"1 = 1";let H=S.map((W)=>j(W,E));return`${R} NOT IN (${H.join(", ")})`}function C(q,E){return`${E.fieldMapper(q.field)} IS NOT NULL`}function G(q,E){let R=E.fieldMapper(q.field),S=j(!0,E);return`${R} = ${S}`}function J(q,E){let R=E.fieldMapper(q.field),S=j(q.min,E),H=j(q.max,E);return`${R} BETWEEN ${S} AND ${H}`}function M(q){switch(q){case"=":case":":return"=";case"!=":return"!=";case"~":return"LIKE";case">":return">";case">=":return">=";case"<":return"<";case"<=":return"<=";default:let E=q;throw Error(`Unknown operator: ${q}`)}}function D(q){switch(q.type){case"string":return q.value;case"number":return q.value;case"boolean":return q.value;case"identifier":return q.value;default:let E=q;throw Error(`Unknown value type: ${q.type}`)}}function j(q,E){if(E.params.push(q),E.parameterStyle==="numbered"){let R=`$${E.paramIndex}`;return E.paramIndex++,R}return"?"}function y(q){return q.replace(/\\/g,"\\\\").replace(/%/g,"\\%").replace(/_/g,"\\_")}function N(q){return`%${y(String(q))}%`}function X(q){return`${y(String(q))}%`}function Y(q){return`%${y(String(q))}`}export{F as toSQL,Y as suffix,X as prefix,y as escapeLike,N as contains};
function b(q,E={}){let R={params:[],parameterStyle:E.parameterStyle??"numbered",fieldMapper:E.fieldMapper??((H)=>H),valueMapper:E.valueMapper??((H)=>H),paramIndex:E.startIndex??1};return{sql:W(q,R),params:R.params}}function W(q,E){switch(q.type){case"or":return z(q,E);case"and":return A(q,E);case"not":return N(q,E);case"comparison":return Q(q,E);case"oneOf":return F(E.fieldMapper(q.field),q.values,E,!1);case"notOneOf":return F(E.fieldMapper(q.field),q.values,E,!0);case"exists":return U(q,E);case"booleanField":return G(q,E);case"range":return J(q,E);default:let R=q;throw Error(`Unknown node type: ${q.type}`)}}function z(q,E){let R=W(q.left,E),S=W(q.right,E);return`(${R} OR ${S})`}function A(q,E){let R=W(q.left,E),S=W(q.right,E);return`(${R} AND ${S})`}function N(q,E){return`NOT (${W(q.expression,E)})`}function Q(q,E){let R=E.fieldMapper(q.field),S=M(q.operator),H=T(q.value);if(q.operator==="~")H=E.valueMapper(H);let j=$(H,E);return`${R} ${S} ${j}`}function F(q,E,R,S){let H=E.length;if(H===0)return S?"1 = 1":"1 = 0";let j=[];for(let D=0;D<H;D++)j.push($(T(E[D]),R));return`${q} ${S?"NOT IN":"IN"} (${j.join(", ")})`}function U(q,E){return`${E.fieldMapper(q.field)} IS NOT NULL`}function G(q,E){let R=E.fieldMapper(q.field),S=$(!0,E);return`${R} = ${S}`}function J(q,E){let R=E.fieldMapper(q.field),S=$(q.min,E),H=$(q.max,E);return`${R} BETWEEN ${S} AND ${H}`}function M(q){switch(q){case"=":case":":return"=";case"!=":return"!=";case"~":return"LIKE";case">":return">";case">=":return">=";case"<":return"<";case"<=":return"<=";default:let E=q;throw Error(`Unknown operator: ${q}`)}}function T(q){switch(q.type){case"string":return q.value;case"number":return q.value;case"boolean":return q.value;case"identifier":return q.value;default:let E=q;throw Error(`Unknown value type: ${q.type}`)}}function $(q,E){if(E.params.push(q),E.parameterStyle==="numbered")return`$${E.paramIndex++}`;return"?"}function y(q){if(!/[\\%_]/.test(q))return q;return q.replace(/[\\%_]/g,(E)=>"\\"+E)}function O(q){return`%${y(String(q))}%`}function X(q){return`${y(String(q))}%`}function Y(q){return`%${y(String(q))}`}export{b as toSQL,Y as suffix,X as prefix,y as escapeLike,O as contains};
//# debugId=7C5E6C130D5FBAFC64756E2164756E21
//# debugId=0FE3C3872A388FA764756E2164756E21
//# sourceMappingURL=index.js.map

@@ -21,3 +21,3 @@ /**

* Parameter placeholder style
* - 'numbered': PostgreSQL/DuckDB style ($1, $2, $3)
* - 'numbered': PostgreSQL/CockroachDB style ($1, $2, $3)
* - 'question': MySQL/SQLite/DuckDB style (?, ?, ?)

@@ -24,0 +24,0 @@ * @default 'numbered'

{
"$schema": "https://json.schemastore.org/package.json",
"name": "@filtron/sql",
"version": "1.1.0",
"description": "SQL WHERE clause generator for Filtron AST with parameterized queries",
"keywords": [
"filtron",
"sql",
"where-clause",
"query-builder",
"ast",
"typescript",
"duckdb",
"postgresql",
"mysql",
"sqlite"
],
"homepage": "https://github.com/jbergstroem/filtron#readme",
"bugs": {
"url": "https://github.com/jbergstroem/filtron/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jbergstroem/filtron.git",
"directory": "packages/sql"
},
"license": "MIT",
"author": "Johan Bergström <bugs@bergstroem.nu>",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"README.md",
"LICENSE"
],
"scripts": {
"bench": "bun --expose-gc run benchmark.ts",
"build": "bun build --minify --splitting --sourcemap=linked --outdir=dist --external @filtron/core index.ts && tsc",
"prepublishOnly": "bun run build && bun test",
"test": "bun test",
"typecheck": "tsc --noemit"
},
"dependencies": {
"@filtron/core": "1.0.0"
},
"peerDependencies": {
"typescript": "5.9.3"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"devDependencies": {
"@types/bun": "1.3.3",
"mitata": "1.0.34",
"typescript": "5.9.3"
},
"engines": {
"node": ">=20.0.0",
"bun": ">=1.1.0"
},
"packageManager": "bun@1.3.3"
"$schema": "https://json.schemastore.org/package.json",
"name": "@filtron/sql",
"version": "1.2.0",
"description": "Filtron helper: generate safe, parameterized SQL WHERE clauses from filter expressions",
"keywords": [
"ast",
"duckdb",
"filtron",
"mysql",
"postgresql",
"query-builder",
"sql",
"sqlite",
"typescript",
"where-clause"
],
"homepage": "https://github.com/jbergstroem/filtron#readme",
"bugs": {
"url": "https://github.com/jbergstroem/filtron/issues"
},
"license": "MIT",
"author": "Johan Bergström <bugs@bergstroem.nu>",
"repository": {
"type": "git",
"url": "git+https://github.com/jbergstroem/filtron.git",
"directory": "packages/sql"
},
"files": [
"dist",
"LICENSE",
"README.md"
],
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"scripts": {
"bench": "bun --expose-gc run benchmark.ts",
"build": "bun build --production --splitting --sourcemap=linked --outdir=dist --external @filtron/core index.ts && tsc",
"prepublishOnly": "bun run build && bun test",
"test": "bun test",
"typecheck": "tsc --noemit"
},
"dependencies": {
"@filtron/core": "workspace:*"
},
"devDependencies": {
"@filtron/benchmark": "workspace:*",
"@types/bun": "catalog:",
"typescript": "catalog:"
},
"peerDependencies": {
"typescript": "catalog:"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"engines": {
"bun": ">=1.1.0",
"node": ">=20.0.0"
},
"packageManager": "bun@1.3.3"
}
+132
-24
# @filtron/sql
SQL WHERE clause generator for [Filtron](https://github.com/jbergstroem/filtron) AST with parameterized queries.
Convert Filtron AST to parameterized SQL WHERE clauses.
[![npm version](https://img.shields.io/npm/v/@filtron/sql.svg)](https://www.npmjs.com/package/@filtron/sql)
[![npm bundle size](https://img.shields.io/bundlephobia/min/%40filtron%2Fsql)](https://bundlephobia.com/package/@filtron/sql)
[![codecov](https://codecov.io/gh/jbergstroem/filtron/graph/badge.svg?token=FXIWJKJ9RI&component=sql)](https://codecov.io/gh/jbergstroem/filtron)
## Installation
```bash
bun add @filtron/sql
npm install @filtron/sql
```
## Quick Start
## Usage

@@ -24,3 +28,3 @@ ```typescript

const users = await db.query(`SELECT * FROM users WHERE ${sql}`, params);
await db.query(`SELECT * FROM users WHERE ${sql}`, params);
}

@@ -31,45 +35,149 @@ ```

### `toSQL(ast, options?)`
### `toSQL(ast, options?): SQLResult`
Converts a Filtron AST to a parameterized SQL WHERE clause.
**Options:**
**Returns:**
| Option | Type | Description |
|--------|------|-------------|
| `parameterStyle` | `'numbered'` \| `'question'` | Placeholder style: `$1` (default) or `?` |
| `fieldMapper` | `(field) => string` | Transform field names to column names |
| `valueMapper` | `(value) => value` | Transform values (useful for LIKE wildcards) |
| `startIndex` | `number` | Starting parameter index (default: `1`) |
```typescript
interface SQLResult {
sql: string; // The WHERE clause (without "WHERE" keyword)
params: unknown[]; // Parameter values in order
}
```
#### Options
| Option | Type | Default | Description |
| ---------------- | ----------------------------- | ------------ | ---------------------------------------- |
| `parameterStyle` | `"numbered"` \| `"question"` | `"numbered"` | Placeholder format |
| `fieldMapper` | `(field: string) => string` | `undefined` | Transform field names to column names |
| `valueMapper` | `(value: unknown) => unknown` | `undefined` | Transform values before parameterization |
| `startIndex` | `number` | `1` | Starting index for numbered placeholders |
#### Parameter styles
**Numbered (`$1`, `$2`, ...)** — PostgreSQL, CockroachDB:
```typescript
const { sql, params } = toSQL(ast);
// sql: "(age > $1 AND status = $2)"
```
**Question marks (`?`, `?`, ...)** — MariaDB, SQLite, DuckDB:
```typescript
const { sql, params } = toSQL(ast, {
parameterStyle: "question", // for MySQL/SQLite
parameterStyle: "question",
});
// sql: "(age > ? AND status = ?)"
```
#### Examples
**Custom field mapping:**
```typescript
const { sql, params } = toSQL(ast, {
fieldMapper: (field) => `users.${field}`,
});
// "age > 18" becomes "users.age > $1"
```
### Helper Functions
**Table-qualified columns:**
Helpers for use with `valueMapper` when using the LIKE operator (`~`):
```typescript
const { sql, params } = toSQL(ast, {
fieldMapper: (field) => `"${field}"`, // Quote column names
});
```
**Start index (for combining queries):**
```typescript
const { sql, params } = toSQL(ast, {
startIndex: 3,
});
// Placeholders start at $3
```
### LIKE helpers
Helper functions for the contains operator (`~`):
```typescript
import { toSQL, contains, prefix, suffix, escapeLike } from "@filtron/sql";
```
// contains("foo") → "%foo%"
// prefix("foo") → "foo%"
// suffix("foo") → "%foo"
// escapeLike("foo%bar") → "foo\\%bar"
| Function | Input | Output | Use case |
| ------------ | ------- | --------- | -------------------- |
| `contains` | `"foo"` | `"%foo%"` | Substring match |
| `prefix` | `"foo"` | `"foo%"` | Starts with |
| `suffix` | `"foo"` | `"%foo"` | Ends with |
| `escapeLike` | `"a%b"` | `"a\\%b"` | Escape special chars |
const { sql, params } = toSQL(ast, { valueMapper: contains });
**Usage with valueMapper:**
```typescript
const { sql, params } = toSQL(ast, {
valueMapper: contains,
});
// Query "name ~ 'john'" produces params: ["%john%"]
```
## Performance
For APIs with repeated filter queries, cache parsed results to avoid redundant parsing:
```typescript
const cache = new Map<string, SQLResult>();
function getFilterSQL(filter: string): SQLResult | null {
const cached = cache.get(filter);
if (cached) return cached;
const result = parse(filter);
if (!result.success) return null;
const sql = toSQL(result.ast, { parameterStyle: "question" });
cache.set(filter, sql);
return sql;
}
```
Consider using an LRU cache with a size limit for production:
```typescript
// https://github.com/isaacs/node-lru-cache
import { LRUCache } from "lru-cache";
const cache = new LRUCache<string, SQLResult>({ max: 1000 });
```
Caching is effective when:
- Users frequently repeat the same filter queries
- Filter expressions are complex (nested `AND`/`OR` conditions)
For simple queries or unique filters, caching overhead is not worthwhile.
Caching at the HTTP level (database query results) is often more effective than caching a parsed Filtron SQLResult. Database queries are typically orders of magnitude slower than processing the Filtron query.
## Security
This library generates parameterized queries to prevent SQL injection:
All queries are parameterized to prevent SQL injection:
```typescript
const { sql, params } = toSQL(ast);
db.query(`SELECT * FROM users WHERE ${sql}`, params);
// Params: ["admin' OR '1'='1"] - safely escaped
// User input with SQL injection attempt
const result = parse('name = "admin\' OR \'1\'=\'1"');
const { sql, params } = toSQL(result.ast);
// sql: "(name = $1)"
// params: ["admin' OR '1'='1"] — treated as literal string value
```
Never interpolate user input directly into SQL. Always use the `params` array with your database driver's parameterized query support.
## License
MIT

Sorry, the diff of this file is not supported yet