<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tejas Kadam</title>
    <description>The latest articles on DEV Community by Tejas Kadam (@tejas821).</description>
    <link>https://dev.to/tejas821</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F570343%2Ffe4a4041-8fed-494b-b3cd-54a929347c35.png</url>
      <title>DEV Community: Tejas Kadam</title>
      <link>https://dev.to/tejas821</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tejas821"/>
    <language>en</language>
    <item>
      <title>sort-filter-lite: headless sort, filter, and pagination for data tables</title>
      <dc:creator>Tejas Kadam</dc:creator>
      <pubDate>Tue, 11 Aug 2026 04:32:42 +0000</pubDate>
      <link>https://dev.to/tejas821/sort-filter-lite-headless-sort-filter-and-pagination-for-data-tables-1nej</link>
      <guid>https://dev.to/tejas821/sort-filter-lite-headless-sort-filter-and-pagination-for-data-tables-1nej</guid>
      <description>&lt;p&gt;Every data table ends up with the same hand-rolled sort/filter/pagination logic, rewritten slightly differently in every project. I got tired of it, so I pulled it into a small package.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sort-filter-lite&lt;/strong&gt; is headless - no components, no rendering assumptions. Just pure functions that work on plain arrays of objects, so it drops into React, Angular, Vue, or vanilla JS the same way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filterBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paginate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sort-filter-lite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;filterBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// { items, page, pageSize, totalItems, totalPages, hasNextPage, hasPrevPage }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few design notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every function returns a new array - nothing mutates the input.&lt;/li&gt;
&lt;li&gt;Default sort handles numbers, dates, and case-insensitive strings automatically. Custom comparator when you need it.&lt;/li&gt;
&lt;li&gt;Filters support eq, neq, gt, gte, lt, lte, contains, startsWith, endsWith, in, notIn - combined with AND semantics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;paginate&lt;/code&gt; clamps out-of-range pages instead of throwing, so "page 0" or "page 999" don't blow up your UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fully typed, zero runtime dependencies, 24 tests, 98% coverage, CI on Node 18 and 20.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/tejas821/sort-filter-lite" rel="noopener noreferrer"&gt;https://github.com/tejas821/sort-filter-lite&lt;/a&gt;&lt;br&gt;
npm: &lt;code&gt;npm i sort-filter-lite&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is package #4 in a small series of open-source utility packages I've been shipping. Feedback welcome - especially if you spot an edge case I missed.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The cross-field form validators Angular doesn't ship (so I built them)</title>
      <dc:creator>Tejas Kadam</dc:creator>
      <pubDate>Tue, 11 Aug 2026 04:04:56 +0000</pubDate>
      <link>https://dev.to/tejas821/the-cross-field-form-validators-angular-doesnt-ship-so-i-built-them-25fe</link>
      <guid>https://dev.to/tejas821/the-cross-field-form-validators-angular-doesnt-ship-so-i-built-them-25fe</guid>
      <description>&lt;p&gt;Third small package in a series I started a couple weeks back (rule-lite, then entitlement-lite). This one's about Angular Reactive Forms.&lt;/p&gt;

&lt;p&gt;Angular ships plenty of single-field validators - required, min, pattern - but almost nothing for cross-field rules. The moment a form needs "end date can't be before start date," or "state is required only if country is US," or "no two line items can share the same SKU," teams either hand-roll a one-off group validator per form or reach for a much heavier form library.&lt;/p&gt;

&lt;p&gt;So I made validators-lite. Three group/array-level validators, each returning a plain ValidatorFn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;validators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;dateRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// { dateRange: {...} } if end is before start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;conditionalRequired and uniqueInArray work the same way - conditionalRequired('country', 'state', c =&amp;gt; c === 'US') marks state required only when the condition is true, and uniqueInArray() fails a FormArray when two entries have the same value (or the same field, for arrays of objects).&lt;/p&gt;

&lt;p&gt;One thing worth calling out: it has no hard dependency on @angular/forms. The validators are typed structurally against a minimal AbstractControlLike interface (.value, .get()), so any real FormGroup or FormArray satisfies them without an adapter - and the package stays usable in tooling that doesn't have Angular resolvable at type-check time.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/tejas821/validators-lite" rel="noopener noreferrer"&gt;https://github.com/tejas821/validators-lite&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/validators-lite" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/validators-lite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've hand-rolled similar validators or have thoughts on the API, I'd like to hear it.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>angular</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a tiny permission checker so I'd stop writing "if user.role === admin" everywhere</title>
      <dc:creator>Tejas Kadam</dc:creator>
      <pubDate>Thu, 30 Jul 2026 18:39:36 +0000</pubDate>
      <link>https://dev.to/tejas821/i-built-a-tiny-permission-checker-so-id-stop-writing-if-userrole-admin-everywhere-4ip6</link>
      <guid>https://dev.to/tejas821/i-built-a-tiny-permission-checker-so-id-stop-writing-if-userrole-admin-everywhere-4ip6</guid>
      <description>&lt;p&gt;Second small package in a series I started last week (first one was rule-lite, a JSON rule evaluator). This one's about permissions.&lt;/p&gt;

&lt;p&gt;Here's the thing about "can this user edit this invoice" - it sounds like a yes/no role question but it almost never is. It's really "can this user edit this invoice, AND do they own it, AND is it under some approval limit." Plain role-based permissions handle the first half fine. Then the second half turns into random if-checks scattered across the codebase, and six months later nobody remembers why that one check exists.&lt;/p&gt;

&lt;p&gt;So I made entitlement-lite. Small idea: roles hold permissions like normal, but any permission can also carry an optional condition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EntitlementEngine&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contributor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resource.ownerId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;u1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;u1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No condition on a permission? It's just a normal role check. Condition present? It gets evaluated at request time using rule-lite (the package I put out last week). I didn't want to write a second rule engine just for this, so it reuses the first one.&lt;/p&gt;

&lt;p&gt;Also supports wildcards so you're not stuck writing &lt;code&gt;{ action: 'view' }, { action: 'edit' }, { action: 'delete' }...&lt;/code&gt; for an admin role - &lt;code&gt;{ action: '*', resource: '*' }&lt;/code&gt; covers it.&lt;/p&gt;

&lt;p&gt;It's deliberately not trying to be OPA or Casbin. Those are the right call for big multi-service auth systems. This is for the much more common case: one app, a handful of roles, a few conditions that need actual logic instead of hardcoded ifs.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/tejas821/entitlement-lite" rel="noopener noreferrer"&gt;https://github.com/tejas821/entitlement-lite&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/entitlement-lite" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/entitlement-lite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've built something similar or have opinions on the API (especially the wildcard matching or how conditions merge into context), I'd like to hear it.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>angular</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why I stopped hardcoding conditional logic in enterprise forms (and open-sourced the fix)</title>
      <dc:creator>Tejas Kadam</dc:creator>
      <pubDate>Wed, 29 Jul 2026 18:13:10 +0000</pubDate>
      <link>https://dev.to/tejas821/why-i-stopped-hardcoding-conditional-logic-in-enterprise-forms-and-open-sourced-the-fix-1gp5</link>
      <guid>https://dev.to/tejas821/why-i-stopped-hardcoding-conditional-logic-in-enterprise-forms-and-open-sourced-the-fix-1gp5</guid>
      <description>&lt;p&gt;Every enterprise app I've worked on eventually hits the same wall: a form or workflow that needs conditional logic — "show field B if A is High," "require C unless D is Approved," "disable E for 30 days after F." The first version is always a few *ngIf*s. Six months later it's an unmaintainable tree of nested conditionals that only the original author fully understands.&lt;/p&gt;

&lt;p&gt;I've rebuilt the fix for this three times now, most recently as the rule engine behind a compliance platform's dynamic audit checklists. This time I pulled the core idea out into a standalone package — rule-lite (&lt;a href="https://github.com/tejas821/rule-lite" rel="noopener noreferrer"&gt;https://github.com/tejas821/rule-lite&lt;/a&gt;) — because the problem is common enough that it shouldn't need reinventing per project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;Rules are data, not code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"any"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.country"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IN"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RuleEngine&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rule-lite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuleEngine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;guest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole API surface: AND/OR/NOT compose to unlimited depth, a dozen built-in operators (eq, gt, in, contains, exists...), and a registerOperator hook for anything domain-specific. Zero runtime dependencies, framework-agnostic — same rule JSON works from an Angular service, a Node backend doing entitlement checks, or a CLI validation script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why object-shaped conditions, not a compact DSL
&lt;/h2&gt;

&lt;p&gt;I considered three shapes before settling on this one:&lt;/p&gt;

&lt;p&gt;A string DSL ("age &amp;gt;= 18 &amp;amp;&amp;amp; country == 'IN'") is the most compact to author, but needs a parser, isn't safely JSON-serializable without escaping, and is hard to validate before evaluation.&lt;/p&gt;

&lt;p&gt;A JsonLogic-style array DSL ({"and": [{"&amp;gt;=": [{"var": "age"}, 18]}]}) is what JsonLogic itself does — very compact, but genuinely unreadable without the spec open next to it.&lt;/p&gt;

&lt;p&gt;Object-shaped conditions ({ field, operator, value }) — what shipped — are more verbose JSON, but self-documenting and trivial to build a form-builder UI around: a select for field, a select for operator, an input for value. That's the actual use case I care about — dynamic forms and business rules that a product or config team, not just engineers, eventually needs to author.&lt;/p&gt;

&lt;p&gt;Readability and "can someone configure this without reading a spec" won over raw compactness.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm using this as the first entry in a small "-lite" family of enterprise-focused utility packages — next up is an RBAC/ABAC permission checker that uses rule-lite internally for attribute-based conditions.&lt;/p&gt;

&lt;p&gt;If you build dynamic forms, feature flags, or business-rule systems in TypeScript and this shape of problem sounds familiar, I'd genuinely like feedback on the API — especially the operator set and whether the all/any/not composition covers real cases you've hit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/tejas821/rule-lite" rel="noopener noreferrer"&gt;https://github.com/tejas821/rule-lite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/rule-lite" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/rule-lite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full design write-up: &lt;a href="https://github.com/tejas821/rule-lite/blob/main/CASE_STUDY.md" rel="noopener noreferrer"&gt;https://github.com/tejas821/rule-lite/blob/main/CASE_STUDY.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>angular</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
