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

@inixiative/json-rules

v1.2.0

Published

TypeScript-first JSON rules engine with intuitive syntax and detailed error messages

Readme

@inixiative/json-rules

A TypeScript-first JSON rules library for:

  • runtime validation with custom error messages
  • Prisma query planning
  • PostgreSQL WHERE generation

The same rule AST can be evaluated against in-memory data with check(), converted into a Prisma query plan with toPrisma(), or compiled into SQL with toSql().

Installation

npm install @inixiative/json-rules
# or
yarn add @inixiative/json-rules
# or
bun add @inixiative/json-rules

Quick Start

import { check, Operator } from '@inixiative/json-rules';

const rule = {
  field: 'age',
  operator: Operator.greaterThanEquals,
  value: 18,
  error: 'Must be 18 or older',
};

check(rule, { age: 21 }); // true
check(rule, { age: 16 }); // "Must be 18 or older"

What It Supports

  • scalar comparisons
  • nested logical conditions with all / any
  • if / then / else
  • array validation against nested object elements
  • date comparisons with timezone-aware runtime evaluation
  • relative value references via path
  • custom error messages on every rule
  • compilation to Prisma and PostgreSQL for supported subsets

Operators

Field Operators

  • equals
  • notEquals
  • lessThan
  • lessThanEquals
  • greaterThan
  • greaterThanEquals
  • contains
  • notContains
  • in
  • notIn
  • matches
  • notMatches
  • between
  • notBetween
  • isEmpty
  • notEmpty
  • exists
  • notExists
  • startsWith
  • endsWith

Array Operators

  • all
  • any
  • none
  • atLeast
  • atMost
  • exactly
  • empty
  • notEmpty

Date Operators

  • before
  • after
  • onOrBefore
  • onOrAfter
  • between
  • notBetween
  • dayIn
  • dayNotIn

Rule Shapes

Field Rule

{
  field: 'status',
  operator: Operator.equals,
  value: 'active'
}

Logical Rules

{
  all: [
    { field: 'age', operator: Operator.greaterThanEquals, value: 18 },
    { field: 'hasLicense', operator: Operator.equals, value: true }
  ]
}

{
  any: [
    { field: 'role', operator: Operator.equals, value: 'admin' },
    { field: 'isOwner', operator: Operator.equals, value: true }
  ]
}

Conditional Rule

{
  if: { field: 'type', operator: Operator.equals, value: 'premium' },
  then: { field: 'discount', operator: Operator.greaterThan, value: 0 },
  else: { field: 'discount', operator: Operator.equals, value: 0 }
}

Array Rule

{
  field: 'orders',
  arrayOperator: ArrayOperator.all,
  condition: {
    field: 'total',
    operator: Operator.lessThanEquals,
    path: '$.maxBudget'
  }
}

Date Rule

{
  field: 'expiryDate',
  dateOperator: DateOperator.after,
  value: '2026-01-01'
}

Path Semantics

path lets a rule resolve its comparison value from somewhere other than value.

Root Context Reference

In runtime validation, a plain path is resolved from the root context:

{
  field: 'confirmPassword',
  operator: Operator.equals,
  path: 'password'
}

Current Array Element Reference

Inside array conditions, $. means "read from the current element":

{
  field: 'orders',
  arrayOperator: ArrayOperator.all,
  condition: {
    field: 'total',
    operator: Operator.lessThanEquals,
    path: '$.maxBudget'
  }
}

Runtime Validation

check() evaluates a rule against data and returns:

  • true when the rule passes
  • a string when the rule fails
import { ArrayOperator, check, Operator } from '@inixiative/json-rules';

const rule = {
  all: [
    { field: 'status', operator: Operator.equals, value: 'active' },
    {
      field: 'orders',
      arrayOperator: ArrayOperator.atLeast,
      count: 2,
      condition: { field: 'status', operator: Operator.equals, value: 'completed' },
    },
  ],
};

check(rule, {
  status: 'active',
  orders: [
    { status: 'completed' },
    { status: 'pending' },
    { status: 'completed' },
  ],
}); // true

Custom Errors

Every rule can define its own error:

{
  field: 'email',
  operator: Operator.matches,
  value: /^[^@]+@[^@]+\.[^@]+$/,
  error: 'Please enter a valid email address'
}

Prisma Query Planning

toPrisma() converts a rule into a Prisma query plan.

import { Operator, toPrisma } from '@inixiative/json-rules';

const plan = toPrisma({
  field: 'status',
  operator: Operator.equals,
  value: 'active',
});

// plan.steps => [{ operation: 'where', where: { status: { equals: 'active' } } }]

Count-based relation filters such as atLeast, atMost, and exactly can produce multi-step plans. Use executePrismaQueryPlan() to resolve groupBy step references before passing the final where into Prisma.

import {
  ArrayOperator,
  Operator,
  executePrismaQueryPlan,
  toPrisma,
} from '@inixiative/json-rules';

const plan = toPrisma(
  {
    field: 'posts',
    arrayOperator: ArrayOperator.atLeast,
    count: 3,
    condition: {
      field: 'published',
      operator: Operator.equals,
      value: true,
    },
  },
  { map, model: 'User' },
);

const where = await executePrismaQueryPlan(plan, { post: prisma.post });
await prisma.user.findMany({ where });

PostgreSQL SQL Generation

toSql() converts a rule into a parameterized PostgreSQL WHERE clause.

import { Operator, toSql } from '@inixiative/json-rules';

const result = toSql({
  field: 'status',
  operator: Operator.equals,
  value: 'active',
});

// {
//   sql: '"status" = $1',
//   params: ['active'],
//   joins: []
// }

With a field map and model, toSql() can generate LEFT JOINs for relation traversal:

const result = toSql(
  { field: 'author.email', operator: Operator.equals, value: '[email protected]' },
  { map, model: 'Post', alias: 't0' },
);

// result.sql   => '"t1"."email" = $1'
// result.joins => ['LEFT JOIN "User" AS "t1" ON "t1"."id" = "t0"."authorId"']

Backend Support Matrix

Not every backend supports every rule shape.

| Capability | check() | toPrisma() | toSql() | | --- | --- | --- | --- | | Field operators | Yes | Most | Yes | | matches / notMatches | Yes | No | Yes | | Logical operators | Yes | Yes | Yes | | Array all / any / none | Yes | Yes | No | | Array atLeast / atMost / exactly | Yes | Yes, with map + model | No | | Array empty / notEmpty | Yes | Yes | Yes | | Date comparisons | Yes | Most | Yes | | dayIn / dayNotIn | Yes | No | Yes | | path: '$.field' current-element / same-row refs | Yes | No | Yes |

Prisma Limitations

  • matches and notMatches are not supported by Prisma output
  • dayIn and dayNotIn are not supported by Prisma output
  • path: '$.field' column-to-column comparisons are not supported by Prisma WHERE
  • count-based relation operators require { map, model }

SQL Limitations

  • complex array element operators are not supported in SQL output:
    • all
    • any
    • none
    • atLeast
    • atMost
    • exactly
  • toSql() generates WHERE fragments and LEFT JOINs, not complete queries

TypeScript Types

The public rule types are generic over comparison payloads:

type Condition<TRuleValue = RuleValue, TDateValue = DateRuleValue> =
  | Rule<TRuleValue>
  | ArrayRule<TRuleValue, TDateValue>
  | DateRule<TDateValue>
  | All<TRuleValue, TDateValue>
  | Any<TRuleValue, TDateValue>
  | IfThenElse<TRuleValue, TDateValue>
  | boolean;

Useful exports:

  • check
  • toPrisma
  • executePrismaQueryPlan
  • toSql
  • validateRule
  • assertValidRule
  • Operator
  • ArrayOperator
  • DateOperator
  • Condition
  • StrictCondition
  • Rule
  • ArrayRule
  • DateRule

Error Handling

The library throws when a rule is structurally invalid, for example:

  • array operators used against non-arrays
  • missing count for count-based array rules
  • invalid date values
  • unsupported backend translations

It returns string errors only from runtime check().

If rules come from JSON, a database, an API, or an editor, validate them first:

import { assertValidRule, validateRule } from '@inixiative/json-rules';

const result = validateRule(rule, { target: 'check' });
if (!result.ok) {
  console.error(result.errors);
}

assertValidRule(rule, { target: 'toPrisma' });

Examples

See examples/basic-validation.ts, examples/array-operations.ts, examples/date-operations.ts, and examples/advanced-features.ts.

License

MIT