npm package: @statedelta-libs/conditions
Description: Declarative condition system with compilation for StateDelta
Author: Anderson D. Rosa
Latest version: 1.0.0

# @statedelta-libs/conditions

> Condicionais declarativas em JSON com compilação para closures otimizadas.

[![npm version](https://img.shields.io/npm/v/@statedelta-libs/conditions.svg)](https://www.npmjs.com/package/@statedelta-libs/conditions)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## 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

```bash
pnpm add @statedelta-libs/conditions
```

## Quick Start

```ts
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'] });           // false
```

## Expressões Simétricas

Ambos os lados de uma condição seguem a mesma regra:

```ts
// 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) // → () => true
```

### JSON puro (o que a UI no-code gera)

```json
{ "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

```ts
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

```ts
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)

```ts
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 }); // false
```

**Casos de uso:**

```ts
// 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

```ts
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:

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
import type {
  Condition,
  ConditionGroup,
  ConditionExpr,
  CompiledCondition,
  Operator,
  Side,
  Ref,
  ValidationResult
} from '@statedelta-libs/conditions';
```

## Licença

MIT © Anderson D. Rosa