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

@cargolift-cdi/business-rules-engine

v0.1.25

Published

Engine de Regras

Readme

Business Rules Engine (Node + TS)

Motor de regras de negócio simples, performático e sem dependências externas. Executa regras em JSON sobre um payload JSON e retorna o payload modificado com auditoria.

Recursos

  • Ações: set, remove, rename, foreach
  • Valores: fixed, path, expression, format, condition e ganchos para lookup/mapping via providers externos
  • Condições: all/any com operadores (=, !=, >, >=, <, <=, in, not_in, contains, starts_with, ends_with, regex, exists, empty, ...)
  • Auditoria: regras aplicadas, ignoradas e erros, com duração total
  • Políticas de erro: default | fail | skip | warn

Uso básico

import { RuleEngine } from './dist/index.js';
import rules from './samples/rules.json' assert { type: 'json' };
import payload from './samples/payload.json' assert { type: 'json' };

const engine = new RuleEngine({ defaultOnError: 'warn' });
const result = await engine.run(rules, payload);
console.log(result.output); // payload transformado
console.log(result.audit);  // auditoria

Consumo em outros projetos

  • ESM (Node 18+ / "type": "module")
import { RuleEngine } from 'business-rules-engine';
  • CommonJS (Jest/NestJS por padrão)
const { RuleEngine } = require('business-rules-engine');

Este pacote publica build dual (ESM + CJS) via "exports" do package.json, com tipos TypeScript incluídos.

Providers (Lookup/Mapping)

Implemente e injete os providers para habilitar lookup e mapping:

const engine = new RuleEngine({
  providers: {
    lookup: { async query({ provider, table, where, select }) { /* ... */ return [] } },
    mapping: { async translate({ table, from, to, input, filters }) { /* ... */ return undefined } }
  }
});

Provider pronto: TypeORM (dynamic)

O pacote expõe um provider pronto com TypeORM, mas carregado dinamicamente para não forçar dependência no core.

Pré-requisito no projeto consumidor: typeorm instalado em runtime.

Configuração por ambiente (mesmo formato do ESB):

LOOKUP_DBS=[{"name":"ods","type":"postgres","host":"localhost","port":5432,"username":"user","password":"pass","database":"db","schema":"public","ssl":false}]

Uso:

import { RuleEngine, TypeormLookupProvider } from '@cargolift-cdi/business-rules-engine';

const engine = new RuleEngine({
  providers: {
    lookup: TypeormLookupProvider.fromEnv(),
  }
});

Desenvolvimento

Testes

  • Executar todos os testes: npm test
  • Modo watch: npm run test:watch

Os testes usam Vitest com TypeScript (ESM). Os arquivos ficam em src/__tests__ e cobrem ações, condições, expressões de data/número, foreach e políticas de erro. Para ver relatório de cobertura, abra a pasta coverage/ gerada após a execução.

Expressões (mode: "expression") — Helpers de Data

O avaliador de expressões suporta chamadas de função (sem eval) e inclui um conjunto de utilitários de data. Você pode usá-los nas regras com mode: "expression". Paths usam JSON Pointer, por exemplo /date_a.

  • Funções disponíveis:

    • now(): Date → data/hora atual
    • date(value?): Date → converte ISO string, timestamp (ms) ou Date; vazio = agora
    • addDays(date, n): Date
    • addMonths(date, n): Date (ajusta fim de mês)
    • addYears(date, n): Date
    • addHours(date, n): Date
    • addMinutes(date, n): Date
    • addSeconds(date, n): Date
    • addMs(date, n): Date
    • toMillis(date): number | fromMillis(ms): Date
    • diffDays(a, b): number | diffHours(a, b): number | diffMillis(a, b): number
    • startOfDay(date): Date | endOfDay(date): Date
    • formatDate(date, mask?): stringmask padrão: "iso"; tokens suportados: YYYY, MM, DD, HH, mm, ss, SSS
  • Operadores com datas:

    • Date + number → Date (soma milissegundos)
    • Date - number → Date (subtrai milissegundos)
    • Date - Date → number (diferença em milissegundos)
    • Date + Date → não suportado (use toMillis/fromMillis se realmente precisar combinar instantes)

Exemplos

{
  "action": "set",
  "target": "/ex/plus_5_days",
  "value": { "mode": "expression", "type": "string", "value": "formatDate(addDays(date(/date_a), 5), \"YYYY-MM-DD\")" },
  "flags": { "createIfMissing": true }
}
{
  "action": "set",
  "target": "/ex/one_hour_later",
  "value": { "mode": "expression", "type": "string", "value": "formatDate(addHours(date(/date_a), 1), \"iso\")" }
}
{
  "action": "set",
  "target": "/ex/diff_days",
  "value": { "mode": "expression", "type": "number", "value": "diffDays(date(/date_b), date(/date_a))" }
}
{
  "action": "set",
  "target": "/ex/op_add_ms",
  "value": { "mode": "expression", "type": "string", "value": "formatDate(date(/date_a) + 86400000, \"iso\")" } // +1 dia
}
{
  "action": "set",
  "target": "/ex/average",
  "value": { "mode": "expression", "type": "string", "value": "formatDate(fromMillis((toMillis(date(/date_a)) + toMillis(date(/date_b))) / 2), \"iso\")" }
}

Notas:

  • Prefira strings ISO (ex.: 2023-08-15T13:45:30Z) para parsing consistente.
  • Para produzir Date nativo no payload, use type: "date" e não formate; para string, use formatDate(...) na expressão ou type: "string".

Troubleshooting: Fuso horário (timezone) e DST

Datas em JavaScript/Node são sensíveis a fuso horário local e horário de verão (DST). Alguns pontos importantes e receitas práticas:

  • Parsing e formatação

    • Strings ISO com sufixo Z são interpretadas como UTC. Ex.: "2023-08-15T13:45:30Z".
    • formatDate(d, "iso") usa toISOString() (sempre UTC).
    • Sem Z, o parser considera o horário local do servidor.
  • startOfDay/endOfDay são LOCAIS

    • startOfDay(date) e endOfDay(date) usam setHours(...) local. Em ambientes com fuso diferente do esperado, o “início do dia” pode não coincidir com UTC 00:00.
    • Se você precisa do limite do dia em UTC, monte a string ISO do dia e parseie:
{
  "action": "set",
  "target": "/ex/utc_start_of_day",
  "value": {
    "mode": "expression",
    "type": "date",
    // UTC 00:00:00 do mesmo dia de /date_a
    "value": "date(formatDate(date(/date_a), \"YYYY-MM-DD\") + 'T00:00:00.000Z')"
  }
}
  • Somar dias com DST
    • addDays(date, 1) usa regras de calendário local; atravessar uma mudança de DST pode resultar em +/- 23h ou 25h de diferença aparente.
    • Se você quer sempre somar blocos fixos de 24h (UTC), some milissegundos em UTC:
{
  "action": "set",
  "target": "/ex/add_1d_utc_exact",
  "value": {
    "mode": "expression",
    "type": "date",
    // 86_400_000 ms = 24h
    "value": "fromMillis(toMillis(date(/date_a)) + 86400000)"
  }
}
  • Comparações de data
    • Para comparar apenas a data (dia) ignorando hora/fuso, normalize ambos os lados antes:
{
  "action": "set",
  "target": "/ex/is_same_utc_day",
  "value": {
    "mode": "expression",
    "type": "boolean",
    "value": "formatDate(date(/a), 'YYYY-MM-DD') == formatDate(date(/b), 'YYYY-MM-DD')"
  }
}
  • Recomendações gerais

    • Padronize as datas do payload em ISO UTC (...Z).
    • Se o domínio exige horário local específico, aplique as funções startOfDay/endOfDay conscientemente (sabendo que são locais) ou normalize via strings ISO como mostrado.
    • Evite “Date + Date”; em vez disso, use toMillis/fromMillis e some durações explícitas.

    Timezone: comportamento padrão

    Para manter o uso simples e previsível:

    • Todas as funções de data do avaliador (addDays/Months/Years/Hours/..., startOfDay/endOfDay) usam horário LOCAL do ambiente (métodos setHours, setDate, etc.).
    • formatDate(d, "iso") sempre usa toISOString() (UTC). Para strings no horário local, use uma máscara como "YYYY-MM-DD HH:mm:ss".
    • Se você precisa operar estritamente em UTC, utilize receitas do capítulo de Troubleshooting (por exemplo, construir strings ISO com sufixo Z e parsear com date(...), ou somar milissegundos com toMillis/fromMillis).