@statedelta-libs/conditions
v0.1.0
Published
Declarative condition system with compilation for StateDelta
Maintainers
Readme
@statedelta-libs/conditions
Sistema de condicionais declarativas com compilação para funções otimizadas.
Características
- JSON puro - Condições serializáveis e auditáveis
- Compilação - Compila uma vez, executa milhões de vezes
- Alta performance - ~5M ops/sec após compilação
- Type-safe - TypeScript nativo com inferência completa
- Zero dependências - Leve e sem bloat
Instalação
npm install @statedelta-libs/conditions
# ou
pnpm add @statedelta-libs/conditionsQuick Start
import { compile, and, eq, contains } from '@statedelta-libs/conditions';
// Criar condição com helpers
const isAdmin = and(
eq('status', 'active'),
contains('roles', 'admin')
);
// Compilar para função
const check = compile(isAdmin);
// Executar (muito rápido!)
check({ status: 'active', roles: ['admin', 'user'] }); // true
check({ status: 'active', roles: ['user'] }); // falseAPI
Operadores
Os seguintes operadores são reconhecidos pelo sistema de conditions:
| Operador | Descrição | Exemplo |
|----------|-----------|---------|
| eq | Igual (===) | eq('status', 'active') |
| neq | Diferente (!==) | neq('status', 'blocked') |
| gt | Maior que | gt('age', 18) |
| gte | Maior ou igual | gte('age', 18) |
| lt | Menor que | lt('count', 10) |
| lte | Menor ou igual | lte('count', 10) |
| contains | Array contém valor | contains('tags', 'vip') |
| notContains | Array não contém | notContains('tags', 'banned') |
| in | Valor está em array | isIn('status', ['a', 'b']) |
| notIn | Valor não está em array | notIn('status', ['x', 'y']) |
| exists | Não é undefined | exists('email') |
| notExists | É undefined | notExists('deletedAt') |
| matches | Regex match | matches('email', '@gmail\\.com$') |
| notMatches | Regex não match | notMatches('email', '@spam\\.com$') |
Importante: Apenas objetos com estes operadores são detectados como conditions pelo
@statedelta-libs/expressions. Objetos com operadores como"set","push","inc"(usados em effects) são tratados como literals, não conditions.
Grupos Lógicos
import { and, or, eq, gt, contains } from '@statedelta-libs/conditions';
// AND - todas devem ser verdadeiras
const condition1 = and(
eq('active', true),
gt('age', 18)
);
// OR - pelo menos uma verdadeira
const condition2 = or(
eq('role', 'admin'),
eq('role', 'moderator')
);
// Aninhado
const condition3 = and(
eq('active', true),
or(
contains('roles', 'admin'),
contains('roles', 'super')
)
);Paths Aninhados
import { compile, eq, gte } from '@statedelta-libs/conditions';
// Acessa user.profile.age
const check = compile(gte('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)
Use { $: "path" } ou o helper ref() para comparar valores de dois caminhos diferentes:
import { compile, cond, ref, and } from '@statedelta-libs/conditions';
// Comparar dois paths
const check = compile({
path: 'user.role',
op: 'eq',
value: { $: 'settings.defaultRole' } // referência!
});
check({ user: { role: 'admin' }, settings: { defaultRole: 'admin' } }); // true
check({ user: { role: 'admin' }, settings: { defaultRole: 'user' } }); // false
// Com helper ref()
const priceCheck = compile(
cond('price', 'lte', ref('budget'))
);
priceCheck({ price: 50, budget: 100 }); // true
priceCheck({ price: 150, budget: 100 }); // false
// Em grupos
const accessCheck = compile(
and(
cond('status', 'eq', 'active'),
cond('userLevel', 'gte', ref('requiredLevel'))
)
);Casos de uso comuns:
// Estoque mínimo
{ path: 'stock', op: 'gte', value: { $: 'minStock' } }
// Preço dentro do orçamento
{ path: 'price', op: 'lte', value: { $: 'customer.budget' } }
// Role permitida
{ path: 'user.role', op: 'in', value: { $: 'config.allowedRoles' } }
// Data de expiração
{ path: 'currentDate', op: 'lt', value: { $: 'subscription.expiresAt' } }Validação
import { validate } from '@statedelta-libs/conditions';
const result = validate({
logic: 'AND',
conditions: [
{ path: 'x', op: 'eq', value: 1 },
{ path: 'y', op: 'invalid' } // erro!
]
});
console.log(result);
// {
// valid: false,
// errors: ['root.conditions[1]: invalid operator "invalid"']
// }Accessor Customizado
Para sistemas que usam métodos get() ao invés de acesso direto por propriedade:
import { compile, eq } from '@statedelta-libs/conditions';
// Contexto com método get()
interface MyContext {
get(path: string): unknown;
}
// Compilar com accessor customizado
const check = compile(eq('hp:value', 100), {
accessor: (path, ctx) => ctx.get(path)
});
// Usar
const ctx: MyContext = { get: (p) => p === 'hp:value' ? 100 : undefined };
check(ctx); // true - usa ctx.get('hp:value')Casos de uso:
- TickContext do StateDelta (
ctx.get("hp:value")) - Map como data source (
map.get(key)) - Resolvers customizados
Cache
import { cache } from '@statedelta-libs/conditions';
// Compila e cacheia automaticamente
const check1 = cache.get({ path: 'x', op: 'eq', value: 1 });
const check2 = cache.get({ path: 'x', op: 'eq', value: 1 });
check1 === check2; // true (mesma referência)
// Gerenciar cache
cache.size; // número de condições cacheadas
cache.clear(); // limpa o cacheJSON Puro
As condições são JSON puro, ideais para armazenar em banco ou transmitir via API:
import { compile } from '@statedelta-libs/conditions';
// Condição como JSON (pode vir de API, banco, arquivo)
const conditionJson = {
logic: 'AND',
conditions: [
{ path: 'user.active', op: 'eq', value: true },
{ path: 'user.age', op: 'gte', value: 18 },
{
logic: 'OR',
conditions: [
{ path: 'user.roles', op: 'contains', value: 'premium' },
{ path: 'user.plan', op: 'eq', value: 'enterprise' }
]
}
]
};
// Compilar e executar
const check = compile(conditionJson);
check(userData);Exemplos Práticos
Validação de Acesso
import { compile, and, eq, contains, gte } from '@statedelta-libs/conditions';
const canAccessPremiumContent = compile(
and(
eq('subscription.active', true),
contains('subscription.features', 'premium-content'),
gte('subscription.daysRemaining', 1)
)
);
// Usar em middleware
function premiumMiddleware(req, res, next) {
if (!canAccessPremiumContent(req.user)) {
return res.status(403).json({ error: 'Premium required' });
}
next();
}Regras de Negócio
import { compile, and, or, eq, gt, lte, contains } from '@statedelta-libs/conditions';
// Desconto VIP: cliente ativo com compras > R$1000 ou tag VIP
const eligibleForVipDiscount = compile(
and(
eq('customer.active', true),
or(
gt('customer.totalPurchases', 1000),
contains('customer.tags', 'vip')
)
)
);
// Frete grátis: pedido > R$150 ou cliente premium
const freeShipping = compile(
or(
gt('order.total', 150),
eq('customer.plan', 'premium')
)
);
// Aplicar regras
function calculateOrder(order, customer) {
const context = { order, customer };
return {
discount: eligibleForVipDiscount(context) ? 0.15 : 0,
shipping: freeShipping(context) ? 0 : 15.90
};
}Filtros Dinâmicos
import { compile, and, gte, lte, eq, isIn } from '@statedelta-libs/conditions';
// Filtros vindos do frontend
const userFilters = {
minPrice: 100,
maxPrice: 500,
category: 'electronics',
brands: ['apple', 'samsung']
};
// Construir condição dinamicamente
const conditions = [];
if (userFilters.minPrice) {
conditions.push(gte('price', userFilters.minPrice));
}
if (userFilters.maxPrice) {
conditions.push(lte('price', userFilters.maxPrice));
}
if (userFilters.category) {
conditions.push(eq('category', userFilters.category));
}
if (userFilters.brands?.length) {
conditions.push(isIn('brand', userFilters.brands));
}
const filter = compile(and(...conditions));
// Filtrar produtos
const filtered = products.filter(filter);Feature Flags
import { compile, and, or, eq, contains, matches } from '@statedelta-libs/conditions';
const featureFlags = {
newDashboard: compile(
or(
contains('user.roles', 'beta-tester'),
eq('user.plan', 'enterprise')
)
),
experimentalApi: compile(
and(
eq('env', 'development'),
matches('user.email', '@company\\.com$')
)
)
};
// Verificar feature
if (featureFlags.newDashboard(context)) {
renderNewDashboard();
} else {
renderLegacyDashboard();
}Eventos e Triggers
import { compile, and, eq, gt, contains } from '@statedelta-libs/conditions';
const triggers = [
{
name: 'welcome-email',
when: compile(eq('event', 'user.created')),
action: sendWelcomeEmail
},
{
name: 'high-value-alert',
when: compile(
and(
eq('event', 'order.placed'),
gt('data.total', 10000)
)
),
action: notifySalesTeam
},
{
name: 'fraud-check',
when: compile(
and(
eq('event', 'payment.processed'),
contains('data.flags', 'high-risk')
)
),
action: triggerFraudReview
}
];
// Event handler
function handleEvent(event) {
for (const trigger of triggers) {
if (trigger.when(event)) {
trigger.action(event);
}
}
}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 |
Otimizações Internas
- Path cache: Getters de path são compilados e cacheados
- Inline functions: Paths com 1-3 níveis usam funções inline
- Early exit: Grupos AND/OR com 4+ condições usam early return
- Set para
in: Arrays com >8 elementos usam Set (O(1) lookup) - Regex pré-compilado: Regex é compilado uma única vez
- LRU cache: Cache com eviction automático
TypeScript
Tipos exportados para uso em aplicações TypeScript:
import type {
Condition,
ConditionGroup,
ConditionExpr,
CompiledCondition,
Operator,
ValidationResult
} from '@statedelta-libs/conditions';
// Tipagem do dado
interface User {
name: string;
age: number;
roles: string[];
}
// Condição tipada
const check: CompiledCondition<User> = compile(
gte('age', 18)
);
check({ name: 'John', age: 21, roles: [] }); // type-safeLicença
MIT © Anderson D. Rosa
