@fiado/rules-engine
v0.4.1
Published
Motor compartido de reglas de visibilidad para lambdas Fiado
Readme
@fiado/rules-engine
Motor compartido de reglas de visibilidad para lambdas Fiado. Lógica pura, sin I/O, con sources pluggables vía ISourceResolver.
Instalación
npm install @fiado/rules-engineConceptos
- Motor (
RulesVisibilityEvaluator) — lógica pura que evalúa estructurasRulesVisibilityagrupando condiciones porsource. Hace UNA sola llamada por source por evaluación (sin N+1). ISourceResolver— contrato pluggable que cada consumer implementa para alimentar al motor con datos (directory, geo, account, etc.).- Operadores — clases aisladas (
EqualsOperator,InOperator,GtOperator, ...) que implementanIOperator. Registradas enOperatorRegistry. RuleEvaluator— evalúa una regla con traza y errores explícitos (ver «Novedades de 0.4.0»).
Uso rápido
import {
RulesVisibilityEvaluator,
OperatorRegistry,
ISourceResolver,
RulesVisibility
} from "@fiado/rules-engine";
// 1. Implementa tus resolvers
class DirectorySourceResolver implements ISourceResolver {
readonly sourceName = "directory";
async resolve(userId: string, fields: string[]): Promise<Record<string, unknown>> {
const profile = await directoryApi.getByIds([userId]);
return pick(profile, fields);
}
}
// 2. Construye el evaluador
const resolvers = new Map<string, ISourceResolver>();
resolvers.set("directory", new DirectorySourceResolver());
const evaluator = new RulesVisibilityEvaluator(
resolvers,
OperatorRegistry.default()
);
// 3. Evalúa items
const items = [
{
id: "ben-3",
rulesVisibility: {
match: "ALL",
groups: [
{
match: "ALL",
conditions: [
{ source: "directory", field: "currency", op: "IN", value: ["USD", "MXN"] }
]
}
]
}
}
];
const visible = await evaluator.evaluate(items, "user-123");Operadores built-in (v0.1.0)
| Operador | Uso |
|----------|-----|
| EQUALS / NOT_EQUALS | comparación estricta |
| IN / NOT_IN | pertenencia a array |
| GT / GTE / LT / LTE | numéricos |
| CONTAINS | substring en string |
| EXISTS | presencia del field |
Para agregar operadores propios:
const registry = OperatorRegistry.default();
registry.register(new MyCustomOperator());Resiliencia ante fallas de resolvers (desde v0.3.0)
Si el resolve() de un source rechaza en runtime (el lambda externo cae, timeout, un error de negocio mal mapeado a excepción, etc.), esa falla se aísla — ya no tumba evaluate() completo:
- Los items que no dependen de ese source se evalúan con normalidad.
- Los items que sí dependen de ese source tratan esa condición puntual como
false(no se puede probar). Si esa condición vive dentro de un grupomatch: "ANY", otra condición sobre un source sano todavía puede hacer pasar al item.
Esto evita el escenario típico de "un banner/beneficio con regla de crédito falla → se ocultan TODOS los banners/beneficios con reglas de esa pantalla, incluso los que no usan crédito".
Esto NO aplica a errores de configuración, que siguen rechazando la evaluación completa porque son bugs de wiring/código, no fallas transitorias — ver MissingSourceResolverError y UnknownOperatorError abajo.
Novedades de 0.4.0 (todo aditivo: nada de 0.3.x cambia)
evaluateOne: resultado con traza (M1) y errores explícitos (M5)
RuleEvaluator evalúa UNA regla para UN contexto y devuelve qué pasó y por qué:
const evaluator = new RuleEvaluator(resolvers, OperatorRegistry.default());
const { outcome, trace } = await evaluator.evaluateOne(rule, ctx);
// outcome: "MATCH" | "NO_MATCH" | "ERROR"
// trace.groups[i].conditions[j]: { source, subject?, field, params?, op, expected, actual?, outcome, error? }Una condición da ERROR (no un false silencioso) si su resolver falla (RESOLVER_FAILED), su sujeto no está en ctx.subjects (SUBJECT_NOT_FOUND), el resolver no entiende params (PARAMS_NOT_SUPPORTED) o el campo no vino y el operador no es EXISTS (FIELD_MISSING). Los grupos combinan con lógica de tres valores (combineOutcomes): en ALL un NO_MATCH gana a un ERROR; en ANY un MATCH gana a un ERROR. evaluate() (el filtro) NO cambia: una condición con error excluye el ítem, como desde 0.3.0.
Parámetros por condición (M2)
Condition.params ({ window: "7d", currency: "USD" }). Para recibirlos, el resolver implementa además IParamSourceResolver.resolveWithParams(ctx, requests); cada pedido trae una key única y el resolver devuelve un mapa key -> valor. Los resolvers que solo implementan resolve(ctx, fields) siguen funcionando sin tocarse; si una condición trae params y el resolver no los entiende, esa condición da PARAMS_NOT_SUPPORTED (no se ignoran).
Varios sujetos (M3)
UserContext.subjects ({ redeemerDirectoryId: { directoryId: "d-2" } }) y Condition.subject: "redeemerDirectoryId". Sin subject la condición usa el ctx actual. El motor llama a cada resolver una vez por (fuente, sujeto).
Validación contra un catálogo (M4)
validateRule(rule, catalog, { allowedSubjects? }) valida estructura, fuente, campo, operador, value, params y subject contra un SourceCatalog y devuelve { valid: true } o { valid: false, errors: string[] }. No lanza.
Errores
MissingSourceResolverError— una condición referencia unsourcesin resolver registrado. Se valida ANTES de resolver nada (eager) — error de configuración, no aislable.UnknownOperatorError— una condición referencia un operador no registrado. Error de código, no aislable.
Desarrollo
npm install
npm run build # compila a bin/
npm test # corre suite completa
npm run lintVersionado
Semver:
- Patch — fixes.
- Minor — operador nuevo, feature no breaking.
- Major — cambio del shape de
RulesVisibility(evitar — rompe data persistida).
Desde 0.4.0 cada versión publicada lleva un tag vX.Y.Z en git.
