@statedelta-libs/conditions
v1.0.0
Published
Declarative condition system with compilation for StateDelta
Maintainers
Readme
@statedelta-libs/conditions
Condicionais declarativas em JSON com compilação para closures otimizadas.
Filosofia
Condições são dados, não código. Uma UI no-code gera JSON, o backend compila para funções nativas. Sem eval, sem string de código, sem interpretação em runtime.
A regra é uma só, nos dois lados da expressão:
{ $: "path" }→ resolve do dado (getter)- qualquer outra coisa → literal constante
Isso torna o sistema simétrico: o lado esquerdo e o direito seguem exatamente a mesma lógica. Zero ambiguidade, zero caso especial.
Instalação
pnpm add @statedelta-libs/conditionsQuick Start
import { compile, and, eq, contains, ref } from '@statedelta-libs/conditions';
// ref() = path no dado. O resto é literal.
const isAdmin = and(
eq(ref('status'), 'active'),
contains(ref('roles'), 'admin')
);
const check = compile(isAdmin);
check({ status: 'active', roles: ['admin', 'user'] }); // true
check({ status: 'active', roles: ['user'] }); // falseExpressões Simétricas
Ambos os lados de uma condição seguem a mesma regra:
// ref vs literal (caso mais comum)
eq(ref('status'), 'active')
// ref vs ref (comparar dois campos)
gt(ref('price'), ref('minPrice'))
// literal vs ref (constante à esquerda)
eq(100, ref('score'))
// literal vs literal (resolve em compile-time)
eq(42, 42) // → () => trueJSON puro (o que a UI no-code gera)
{ "left": { "$": "user.role" }, "op": "eq", "right": "admin" }
{ "left": { "$": "score" }, "op": "gt", "right": { "$": "minScore" } }
{ "left": 100, "op": "eq", "right": { "$": "score" } }API
Operadores
| Operador | Descrição | Exemplo |
|----------|-----------|---------|
| eq | Igual (===) | eq(ref('status'), 'active') |
| neq | Diferente (!==) | neq(ref('status'), 'blocked') |
| gt | Maior que | gt(ref('age'), 18) |
| gte | Maior ou igual | gte(ref('age'), 18) |
| lt | Menor que | lt(ref('count'), 10) |
| lte | Menor ou igual | lte(ref('count'), 10) |
| contains | Array contém valor | contains(ref('tags'), 'vip') |
| notContains | Array não contém | notContains(ref('tags'), 'banned') |
| in | Valor está em array | isIn(ref('status'), ['a', 'b']) |
| notIn | Valor não está em array | notIn(ref('status'), ['x', 'y']) |
| exists | Não é undefined | exists(ref('email')) |
| notExists | É undefined | notExists(ref('deletedAt')) |
| matches | Regex match | matches(ref('email'), '@gmail\\.com$') |
| notMatches | Regex não match | notMatches(ref('email'), '@spam\\.com$') |
Grupos Lógicos
import { and, or, eq, gt, ref } from '@statedelta-libs/conditions';
// AND - todas devem ser verdadeiras
and(
eq(ref('active'), true),
gt(ref('age'), 18)
)
// OR - pelo menos uma verdadeira
or(
eq(ref('role'), 'admin'),
eq(ref('role'), 'moderator')
)
// Aninhado
and(
eq(ref('active'), true),
or(
contains(ref('roles'), 'admin'),
contains(ref('roles'), 'super')
)
)Paths Aninhados
import { compile, gte, ref } from '@statedelta-libs/conditions';
const check = compile(gte(ref('user.profile.age'), 18));
check({ user: { profile: { age: 21 } } }); // true
check({ user: { profile: { age: 15 } } }); // false
check({ user: {} }); // false (path não existe)Referências (Comparação entre Paths)
import { compile, cond, ref, and } from '@statedelta-libs/conditions';
// Dois paths comparados
const check = compile({
left: { $: 'user.role' },
op: 'eq',
right: { $: 'settings.defaultRole' }
});
check({ user: { role: 'admin' }, settings: { defaultRole: 'admin' } }); // true
// Com builders
const priceCheck = compile(
cond(ref('price'), 'lte', ref('budget'))
);
priceCheck({ price: 50, budget: 100 }); // true
priceCheck({ price: 150, budget: 100 }); // falseCasos de uso:
// Estoque mínimo
{ left: { $: 'stock' }, op: 'gte', right: { $: 'minStock' } }
// Preço dentro do orçamento
{ left: { $: 'price' }, op: 'lte', right: { $: 'customer.budget' } }
// Role permitida
{ left: { $: 'user.role' }, op: 'in', right: { $: 'config.allowedRoles' } }Validação
import { validate } from '@statedelta-libs/conditions';
const result = validate({
logic: 'AND',
conditions: [
{ left: { $: 'x' }, op: 'eq', right: 1 },
{ left: { $: 'y' }, op: 'invalid' }
]
});
// { valid: false, errors: ['root.conditions[1]: invalid operator "invalid"'] }Accessor Customizado
Para sistemas que usam get() ao invés de acesso direto por propriedade:
import { compile, eq, ref } from '@statedelta-libs/conditions';
const check = compile(eq(ref('hp:value'), 100), {
accessor: (path, ctx) => ctx.get(path)
});
check(tickContext); // usa ctx.get('hp:value')Cache
import { cache } from '@statedelta-libs/conditions';
const check1 = cache.get({ left: { $: 'x' }, op: 'eq', right: 1 });
const check2 = cache.get({ left: { $: 'x' }, op: 'eq', right: 1 });
check1 === check2; // true (mesma referência)Exemplos Práticos
Regras de Negócio
import { compile, and, or, eq, gt, contains, ref } from '@statedelta-libs/conditions';
// Desconto VIP
const eligibleForVipDiscount = compile(
and(
eq(ref('customer.active'), true),
or(
gt(ref('customer.totalPurchases'), 1000),
contains(ref('customer.tags'), 'vip')
)
)
);
// Frete grátis
const freeShipping = compile(
or(
gt(ref('order.total'), 150),
eq(ref('customer.plan'), 'premium')
)
);Filtros Dinâmicos
import { compile, and, gte, lte, eq, isIn, ref } from '@statedelta-libs/conditions';
const conditions = [];
if (filters.minPrice) conditions.push(gte(ref('price'), filters.minPrice));
if (filters.maxPrice) conditions.push(lte(ref('price'), filters.maxPrice));
if (filters.category) conditions.push(eq(ref('category'), filters.category));
if (filters.brands?.length) conditions.push(isIn(ref('brand'), filters.brands));
const filter = compile(and(...conditions));
const filtered = products.filter(filter);Feature Flags
import { compile, or, and, eq, contains, matches, ref } from '@statedelta-libs/conditions';
const featureFlags = {
newDashboard: compile(
or(
contains(ref('user.roles'), 'beta-tester'),
eq(ref('user.plan'), 'enterprise')
)
),
experimentalApi: compile(
and(
eq(ref('env'), 'development'),
matches(ref('user.email'), '@company\\.com$')
)
)
};Performance
| Operação | Velocidade | |----------|------------| | Compilação | ~100,000 ops/sec | | Execução (pós-compile) | ~5,000,000 ops/sec | | Cache hit | ~10,000,000 ops/sec |
TypeScript
import type {
Condition,
ConditionGroup,
ConditionExpr,
CompiledCondition,
Operator,
Side,
Ref,
ValidationResult
} from '@statedelta-libs/conditions';Licença
MIT © Anderson D. Rosa
