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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@nam088/json-logic-to-sql

v1.9.0

Published

A powerful, secure, and type-safe library to convert JSON Logic rules into SQL WHERE clauses with comprehensive schema validation and multi-dialect support

Readme

@nam088/json-logic-to-sql

License TypeScript

A powerful, secure, and type-safe library to convert JSON Logic rules into SQL WHERE clauses with comprehensive schema validation and multi-dialect support.

Features

  • Secure by Default: Generates parameterized SQL queries to prevent SQL injection attacks
  • Multi-Dialect Support: Works with PostgreSQL, MySQL, MSSQL (SQL Server), and SQLite
  • Type-Safe: Written in TypeScript with comprehensive type definitions and strict validation
  • Schema Validation: Validates rules against a defined schema before compilation with customizable constraints
  • Advanced Field Mapping: Support for column mapping, JSONB paths, and computed fields
  • Field Transformations: Built-in SQL transforms (LOWER, UPPER, TRIM, DATE functions, etc.)
  • Flexible Operators: Comprehensive operator support including comparison, logical, string, array, and JSONB operations
  • Query Utilities: Built-in helpers for pagination, sorting, and SELECT clause generation
  • Extensible: Support for custom transforms and field constraints

Installation

npm install @nam088/json-logic-to-sql

Quick Start

1. Define your Schema

Define the fields that are allowed to be queried with their types, operators, and optional constraints.

import { FilterSchema } from '@nam088/json-logic-to-sql';

const schema: FilterSchema = {
  fields: {
    firstName: {
      type: 'string',
      operators: ['eq', 'ne', 'like', 'in'],
      column: 'first_name', // Maps 'firstName' to 'first_name' column
    },
    age: {
      type: 'number',
      operators: ['eq', 'gt', 'lt', 'gte', 'lte', 'between'],
      constraints: {
        min: 0,
        max: 150,
      },
    },
    status: {
      type: 'string',
      operators: ['eq', 'in'],
      options: {
        items: [
          { value: 'active', label: 'Active' },
          { value: 'inactive', label: 'Inactive' },
          { value: 'pending', label: 'Pending' },
        ],
        strict: true, // Only allow predefined values
      },
    },
    email: {
      type: 'string',
      operators: ['eq', 'like', 'ilike'],
      constraints: {
        pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      },
    },
  },
  settings: {
    maxDepth: 5,
    maxConditions: 100,
  },
};

2. Initialize the Compiler

import { JsonLogicCompiler } from '@nam088/json-logic-to-sql';

const compiler = new JsonLogicCompiler({
  schema,
  dialect: 'postgresql', // or 'mysql', 'mssql', 'sqlite'
});

3. Compile Rules

const rule = {
  and: [
    { '==': [{ var: 'status' }, 'active'] },
    { '>': [{ var: 'age' }, 18] }
  ]
};

try {
  const result = compiler.compile(rule);
  console.log(result.sql);
  // Output: (status = $1 AND age > $2)
  
  console.log(result.params);
  // Output: { '$1': 'active', '$2': 18 }
} catch (error) {
  console.error('Compilation failed:', error.message);
}

Core Concepts

Field Schema

Each field in your schema can have:

{
  type: FieldType;              // Data type (string, number, boolean, date, array, etc.)
  operators: Operator[];        // Allowed operators for this field
  column?: string;              // Database column name (if different from field name)
  filterable?: boolean;         // Allow in WHERE clauses (default: true)
  selectable?: boolean;         // Allow in SELECT clauses (default: true)
  sortable?: boolean;           // Allow in ORDER BY (default: true)
  nullable?: boolean;           // Allow null values
  caseSensitive?: boolean;      // For string comparisons
  options?: OptionConfig;       // Predefined values
  constraints?: FieldConstraints; // Validation rules
  transform?: FieldTransform;   // SQL transformations
  jsonPath?: string;            // JSONB path for nested data
  meta?: Record<string, unknown>; // Custom metadata
}

Computed Fields

Define fields that are calculated from SQL expressions:

const schema: FilterSchema = {
  fields: {
    fullName: {
      type: 'string',
      operators: ['like', 'ilike'],
      computed: true,
      expression: "first_name || ' ' || last_name",
    },
    ageGroup: {
      type: 'string',
      operators: ['eq', 'in'],
      computed: true,
      expression: "CASE WHEN age < 18 THEN 'minor' ELSE 'adult' END",
    },
  },
};

Field Transformations

Apply SQL transformations to fields:

const schema: FilterSchema = {
  fields: {
    email: {
      type: 'string',
      operators: ['eq', 'like'],
      transform: {
        input: 'lower', // Apply LOWER() when filtering
        output: 'lower', // Apply LOWER() when selecting
      },
    },
    createdAt: {
      type: 'datetime',
      operators: ['eq', 'gt', 'lt'],
      transform: {
        input: 'date', // Extract date part for comparison
      },
    },
  },
};

Built-in transforms: lower, upper, trim, ltrim, rtrim, unaccent, date, year, month, day

Custom transforms:

transform: {
  input: {
    name: 'custom',
    sql: 'CUSTOM_FUNCTION({column})'
  }
}

Field Constraints

Validate values before compilation:

constraints: {
  // String constraints
  minLength: 3,
  maxLength: 100,
  pattern: /^[a-zA-Z]+$/,
  
  // Number constraints
  min: 0,
  max: 1000,
  
  // Array constraints
  minItems: 1,
  maxItems: 10,
  
  // Date constraints
  dateFormat: 'YYYY-MM-DD',
  minDate: '2020-01-01',
  maxDate: '2025-12-31',
  
  // Custom validation
  validate: (value) => {
    if (typeof value === 'string' && value.includes('admin')) {
      return 'Cannot filter by admin values';
    }
    return true;
  }
}

Supported Operators

Comparison Operators

| JSON Logic | Internal | SQL | Description | |------------|----------|-----|-------------| | ==, === | eq | = | Equal | | !=, !== | ne | <> | Not equal | | > | gt | > | Greater than | | >= | gte | >= | Greater than or equal | | < | lt | < | Less than | | <= | lte | <= | Less than or equal |

Logical Operators

  • and - Logical AND
  • or - Logical OR
  • !, not - Logical NOT

String Operators

| Operator | Description | Example | |----------|-------------|---------| | like | SQL LIKE (case-sensitive) | { like: [{ var: 'name' }, 'John%'] } | | ilike | Case-insensitive LIKE (PostgreSQL) | { ilike: [{ var: 'name' }, 'john%'] } | | starts_with | Starts with string | { starts_with: [{ var: 'name' }, 'John'] } | | ends_with | Ends with string | { ends_with: [{ var: 'email' }, '.com'] } | | contains | Contains substring | { contains: [{ var: 'description' }, 'urgent'] } | | regex | Regular expression match | { regex: [{ var: 'code' }, '^[A-Z]{3}'] } |

Array Operators

| Operator | Description | Example | |----------|-------------|---------| | in | Value in list | { in: [{ var: 'status' }, ['active', 'pending']] } | | not_in | Value not in list | { not_in: [{ var: 'status' }, ['deleted']] } | | between | Value between two values | { between: [{ var: 'age' }, 18, 65] } | | not_between | Value not between | { not_between: [{ var: 'price' }, 100, 200] } |

Array Column Operators (PostgreSQL)

For fields with type: 'array':

| Operator | Description | Example | |----------|-------------|---------| | contains | Array contains values | { contains: [{ var: 'tags' }, ['urgent', 'bug']] } | | contained_by | Array contained by values | { contained_by: [{ var: 'tags' }, ['all', 'possible', 'tags']] } | | overlaps | Arrays overlap | { overlaps: [{ var: 'categories' }, ['tech', 'news']] } | | any_of | Value equals any element | { any_of: [{ var: 'tags' }, 'urgent'] } | | not_any_of | Value not in array | { not_any_of: [{ var: 'tags' }, 'spam'] } |

JSONB Operators (PostgreSQL)

| Operator | Description | Example | |----------|-------------|---------| | json_contains | JSONB contains | { json_contains: [{ var: 'metadata' }, {"key": "value"}] } | | json_has_key | JSONB has key | { json_has_key: [{ var: 'metadata' }, 'priority'] } | | json_has_any_keys | JSONB has any keys | { json_has_any_keys: [{ var: 'metadata' }, ['key1', 'key2']] } |

Null Check Operators

| Operator | Description | Example | |----------|-------------|---------| | is_null | Value is NULL | { is_null: [{ var: 'deletedAt' }] } | | is_not_null | Value is not NULL | { is_not_null: [{ var: 'email' }] } |

Database Dialect Support

| Dialect | Key | Placeholder Style | Notes | |---------|-----|-------------------|-------| | PostgreSQL | postgresql | $1, $2, $3, ... | Full feature support including ILIKE, array operators, JSONB | | MySQL | mysql | ?, ?, ?, ... | Standard SQL features | | SQL Server | mssql | @p1, @p2, @p3, ... | Standard SQL features | | SQLite | sqlite | ?, ?, ?, ... | Standard SQL features |

Advanced Features

Schema Mapping Utilities

Separate public schema (for frontend) from internal database schema:

import { applyFieldMappings, toPublicSchema } from '@nam088/json-logic-to-sql';

// Define public schema (clean, no DB details)
const publicSchema: FilterSchema = {
  fields: {
    userName: { type: 'string', operators: ['eq', 'like'] },
    userEmail: { type: 'string', operators: ['eq'] },
    tags: { type: 'array', operators: ['contains'] },
    priority: { type: 'string', operators: ['eq'] },
  },
};

// Apply internal mappings on backend
const internalSchema = applyFieldMappings(publicSchema, {
  columns: {
    userName: { table: 'users', column: 'name' },
    userEmail: { table: 'users', column: 'email' },
    tags: '_tags', // Simple column rename
  },
  jsonPaths: {
    priority: "metadata->>'priority'", // JSONB path
  },
});

// Send public schema to frontend (without internal details)
const schemaForFrontend = toPublicSchema(internalSchema);

Pagination and Sorting

import { buildPagination, buildSort } from '@nam088/json-logic-to-sql';

// Build pagination
const pagination = buildPagination(
  { page: 2, pageSize: 20 },
  100, // max page size
  1    // starting param index
);
console.log(pagination.sql);
// Output: LIMIT $1 OFFSET $2
console.log(pagination.params);
// Output: { '$1': 20, '$2': 20 }

// Build sorting
const sort = buildSort(
  [
    { field: 'createdAt', direction: 'desc' },
    { field: 'name', direction: 'asc' },
  ],
  schema
);
console.log(sort.sql);
// Output: ORDER BY "created_at" DESC, "name" ASC

SELECT Clause Generation

import { buildSelect } from '@nam088/json-logic-to-sql';

const select = buildSelect(schema, {
  fields: ['firstName', 'lastName', 'email'],
  exclude: ['password'],
});
console.log(select.sql);
// Output: "first_name" AS "firstName", "last_name" AS "lastName", "email"

Complete Query Example

const compiler = new JsonLogicCompiler({ schema, dialect: 'postgresql' });

// Compile WHERE clause
const where = compiler.compile({
  and: [
    { '==': [{ var: 'status' }, 'active'] },
    { '>': [{ var: 'age' }, 18] }
  ]
});

// Build SELECT
const select = buildSelect(schema, {
  fields: ['firstName', 'lastName', 'email', 'age']
});

// Build ORDER BY
const sort = buildSort([{ field: 'lastName', direction: 'asc' }], schema);

// Build LIMIT/OFFSET
const pagination = buildPagination({ page: 1, pageSize: 20 });

// Combine into full query
const query = `
  SELECT ${select.sql}
  FROM users
  WHERE ${where.sql}
  ${sort.sql}
  ${pagination.sql}
`;

const allParams = { ...where.params, ...pagination.params };

Error Handling

The library throws specific error types for different scenarios:

import { 
  CompilerError, 
  SchemaValidationError 
} from '@nam088/json-logic-to-sql';

try {
  const result = compiler.compile(rule);
} catch (error) {
  if (error instanceof SchemaValidationError) {
    // Schema validation failed
    console.error('Field:', error.field);
    console.error('Operator:', error.operator);
    console.error('Message:', error.message);
  } else if (error instanceof CompilerError) {
    // Compilation error (invalid rule structure, etc.)
    console.error('Compilation error:', error.message);
  }
}

Security

The library implements multiple security layers:

  1. Parameterized Queries: All values are passed as parameters, never interpolated
  2. Identifier Escaping: Column and table names are properly quoted
  3. Schema Validation: Only allowed fields and operators can be used
  4. Input Sanitization: Deep sanitization of input rules
  5. Constraint Validation: Values are validated against defined constraints
  6. Depth Limiting: Prevents deeply nested rules that could cause performance issues
  7. Condition Limiting: Prevents queries with excessive conditions

TypeScript Support

Full TypeScript support with comprehensive type definitions:

import type {
  FilterSchema,
  FieldSchema,
  ComputedFieldSchema,
  CompilerConfig,
  SqlResult,
  Operator,
  FieldType,
  FieldConstraints,
  FieldTransform,
  TransformFn,
  CustomTransform,
  OptionConfig,
  OptionItem,
  LookupRegistry,
  LookupConfig,
} from '@nam088/json-logic-to-sql';

Examples

479: 480: For more comprehensive examples, check out the examples directory or the unit tests which cover all supported scenarios.

Complex Nested Query

const rule = {
  or: [
    {
      and: [
        { '==': [{ var: 'status' }, 'active'] },
        { '>': [{ var: 'age' }, 18] },
        { 'in': [{ var: 'country' }, ['US', 'CA', 'UK']] }
      ]
    },
    {
      and: [
        { '==': [{ var: 'role' }, 'admin'] },
        { 'is_not_null': [{ var: 'lastLogin' }] }
      ]
    }
  ]
};

const result = compiler.compile(rule);
// Generates: ((status = $1 AND age > $2 AND country IN ($3, $4, $5)) OR (role = $6 AND last_login IS NOT NULL))

Array Operations (PostgreSQL)

const schema: FilterSchema = {
  fields: {
    tags: {
      type: 'array',
      operators: ['contains', 'overlaps', 'any_of'],
    },
  },
};

// Check if array contains all values
const rule1 = { contains: [{ var: 'tags' }, ['urgent', 'bug']] };
// SQL: tags @> $1  (where $1 = ['urgent', 'bug'])

// Check if arrays overlap
const rule2 = { overlaps: [{ var: 'tags' }, ['tech', 'news']] };
// SQL: tags && $1

// Check if value is in array column
const rule3 = { any_of: [{ var: 'tags' }, 'urgent'] };
// SQL: $1 = ANY(tags)

JSONB Queries (PostgreSQL)

const schema: FilterSchema = {
  fields: {
    metadata: {
      type: 'jsonb',
      operators: ['json_contains', 'json_has_key'],
    },
    priority: {
      type: 'string',
      operators: ['eq', 'in'],
      jsonPath: "metadata->>'priority'",
    },
  },
};

const rule = {
  and: [
    { json_has_key: [{ var: 'metadata' }, 'priority'] },
    { '==': [{ var: 'priority' }, 'high'] }
  ]
};

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT

Author

@nam088