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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@statedelta-libs/expressions

v0.0.3

Published

JSON DSL compiler for optimized functions - StateDelta expression engine

Readme

@statedelta-libs/expressions

Compilador de JSON DSL para funções otimizadas.

npm version License: MIT

Características

  • Compilação - Compila uma vez, executa milhões de vezes
  • Alta performance - ~25-30M ops/s após compilação
  • Scope externo - Funções vêm via scope, não hardcoded
  • Accessor customizado - Suporte a ctx.get(path) para objetos especiais
  • $pipe - Composição com valor inicial (sintaxe DSL)
  • Path syntax - Suporte a wildcards (items[*].price)
  • Dependency extraction - Para dirty tracking/reatividade
  • Integração - Usa @statedelta-libs/conditions para condicionais
  • Type-safe - TypeScript nativo com inferência

Instalação

npm install @statedelta-libs/expressions
# ou
pnpm add @statedelta-libs/expressions

Quick Start

import { compile, evaluate } from '@statedelta-libs/expressions';
import { filter, map, sum } from '@statedelta-libs/operators';

// Define o scope com funções disponíveis
const scope = { filter, map, sum };

// Compilar expressão com $pipe
const expr = compile({
  $pipe: [
    { $: "items" },
    { $fn: "filter", args: [(item) => item.active] },
    { $fn: "map", args: [(item) => item.price] },
    { $fn: "sum" }
  ]
}, { scope });

// Executar (muito rápido!)
const data = {
  items: [
    { name: "A", price: 10, active: true },
    { name: "B", price: 20, active: false },
    { name: "C", price: 30, active: true }
  ]
};

expr.fn(data);  // 40
expr.deps;      // ["items"]

API

compile()

Compila expressão JSON DSL para função otimizada usando closures.

const { fn, deps, hash } = compile(expression, { scope });

fn(data);     // Executa
deps;         // Paths que a expressão depende
hash;         // Hash único (para cache)

compileAST()

Compila usando AST + new Function(). Mais rápido na execução, ideal para expressões executadas muitas vezes.

import { compileAST } from '@statedelta-libs/expressions';

const { fn, deps, hash } = compileAST(expression, { scope });

fn(data);     // Executa (mais rápido que compile())

Quando usar cada um:

| Cenário | Recomendação | Por quê | |---------|--------------|---------| | Execução única | compile() | Compilação mais rápida | | Poucas execuções (<8x) | compile() | Overhead de AST não compensa | | Muitas execuções (>8x) | compileAST() | Execução ~25-170% mais rápida | | Hot path crítico | compileAST() | V8 JIT otimiza melhor | | Expressões complexas | compileAST() | Ganho maior em nested calls |

evaluate() / evaluateAST()

Compila e executa em um passo.

const result = evaluate(
  { $fn: "add", args: [{ $: "a" }, { $: "b" }] },
  { a: 1, b: 2 },
  { scope: { add: (a, b) => a + b } }
);
// 3

// Versão AST
const result = evaluateAST(expression, data, { scope });

extractDeps()

Extrai dependências sem compilar.

const deps = extractDeps({
  $if: "$isVip",
  then: { $: "price.vip" },
  else: { $: "price.regular" }
});
// ["$isVip", "price.vip", "price.regular"]

Tipos de Expressão

Literal

42
"hello"
true
[1, 2, 3]
{ a: 1 }

Reference ($)

{ $: "user.name" }           // Acessa user.name
{ $: "items[0].price" }      // Acessa índice
{ $: "items[*].price" }      // Wildcard → [10, 20, 30]

Conditional ($if)

{
  $if: { path: "age", op: "gte", value: 18 },
  then: "adult",
  else: "minor"
}

// Shorthand
{ $if: "$isVip", then: 0.2, else: 0 }
{ $if: "!$isBlocked", then: "allowed" }

Pipe ($pipe)

Composição com valor inicial - o valor passa por cada função em sequência.

{
  $pipe: [
    { $: "items" },                              // Valor inicial
    { $fn: "filter", args: [predicateFn] },      // Retorna função curried
    { $fn: "map", args: [mapperFn] },            // Retorna função curried
    { $fn: "sum" }                               // Referência à função
  ]
}

Function Call ($fn)

Chama função do scope com argumentos compilados.

// Com args: chama a função
{ $fn: "add", args: [{ $: "a" }, { $: "b" }] }
// → scope.add(data.a, data.b)

// Sem args: retorna referência à função (útil em $pipe)
{ $fn: "sum" }
// → scope.sum

// Com args vazio: chama função sem argumentos
{ $fn: "getTimestamp", args: [] }
// → scope.getTimestamp()

Condition (delegado)

// Detectado automaticamente e delegado para @statedelta-libs/conditions
{ path: "user.age", op: "gte", value: 18 }

Nota: Apenas objetos com operadores de condition válidos (eq, neq, gt, gte, lt, lte, in, notIn, contains, notContains, exists, notExists, matches, notMatches, startsWith, endsWith) são detectados como conditions. Objetos com op: "set" ou outros operadores não são conditions e são tratados como literals.

Scope

O scope define quais funções estão disponíveis para $fn:

import * as R from '@statedelta-libs/operators';

// Todas as funções do operators
const scope = R;

// Ou seleção específica
const scope = {
  add: R.add,
  filter: R.filter,
  map: R.map,
  sum: R.sum,
  // Funções custom
  double: (n) => n * 2,
  isActive: (item) => item.active
};

const { fn } = compile(expression, { scope });

Accessor Customizado

Para objetos que usam interface de acesso customizada (como ctx.get(path)), use o accessor:

// Contexto com método get() customizado
interface TickContext {
  get(path: string): unknown;
}

const accessor = (path: string, ctx: TickContext) => ctx.get(path);

// Compilação com accessor
const { fn } = compile<TickContext>(
  { $: "hp:value" },
  { accessor }
);

fn(tickContext); // usa ctx.get('hp:value')

// Com scope + accessor
const { fn } = compile<TickContext>(
  { $fn: "add", args: [{ $: "a" }, { $: "b" }] },
  { scope, accessor }
);

O accessor é propagado para:

  • Referências ({ $: "path" })
  • Shorthand de $if ({ $if: "path", ... })
  • Argumentos de $fn
  • Steps de $pipe
  • Conditions (delegadas para @statedelta-libs/conditions)

Performance: Igual ao acesso direto (~25-30M ops/s).

Path Syntax

| Path | Resultado | |------|-----------| | user | data.user | | user.name | data.user.name | | items[0] | data.items[0] | | items[*].price | data.items.map(i => i.price) |

Cache

import { cache, cached } from '@statedelta-libs/expressions';

// Com scope
const compiled = cache.get(expression, { scope });

// Ou função helper
const compiled = cached(expression, { scope });

// Gerenciar
cache.size;    // Tamanho atual
cache.clear(); // Limpar

Performance

compile() - Closures

| Operação | Velocidade | |----------|------------| | Compile (literal) | ~6.5M ops/s | | Compile (ref) | ~3M ops/s | | Compile (fn nested) | ~550K ops/s | | Compile (complex) | ~300K ops/s | | Execute (ref) | ~28-30M ops/s | | Execute (fn nested) | ~9M ops/s | | Execute (com accessor) | ~23-30M ops/s |

compileAST() - AST + new Function

| Operação | Velocidade | |----------|------------| | Compile (literal) | ~1.2M ops/s | | Compile (ref) | ~800K ops/s | | Compile (fn nested) | ~230K ops/s | | Compile (complex) | ~130K ops/s | | Execute (ref) | ~27M ops/s | | Execute (fn nested) | ~25M ops/s ⚡ |

Comparação Execução

| Cenário | Closures | AST | Ganho AST | |---------|----------|-----|-----------| | fn nested | 9M ops/s | 25M ops/s | +169% | | ref 4 níveis | 16M ops/s | 27M ops/s | +69% | | conditional nested | 20M ops/s | 25M ops/s | +25% |

Break-even: ~8 execuções - após isso, compileAST() compensa o tempo extra de compilação.

Segurança

Ambas as abordagens são seguras contra injeção de código:

| Método | Proteção | |--------|----------| | compile() | Não gera código string - apenas compõe funções | | compileAST() | Usa destructuring que valida identificadores automaticamente |

// Tentativa de injeção - FALHA em ambos
{ $fn: "add; console.log('hacked'); //" }

// compile(): tenta acessar scope["add; console.log..."] → undefined
// compileAST(): destructuring inválido → SyntaxError

Funções só são acessíveis se existirem no scope fornecido pelo desenvolvedor.

Type Detection

O compilador detecta automaticamente o tipo de expressão baseado na estrutura do objeto:

| Tipo | Detecção | Exemplo | |------|----------|---------| | Reference | { $ } presente | { $: "user.name" } | | Conditional | { $if, then } presentes | { $if: cond, then: x, else: y } | | Function | { $fn } presente | { $fn: "add", args: [...] } | | Pipe | { $pipe } presente | { $pipe: [...] } | | Condition | { path, op } com operador válido | { path: "age", op: "gte", value: 18 } | | Literal | Nenhum dos acima | { foo: "bar" }, 42, "hello" |

Distinção entre Conditions e Effect Objects

Objetos com path e op só são tratados como conditions se op for um operador válido:

// ✓ Condition (op: "eq" é operador válido)
{ path: "user.age", op: "eq", value: 18 }

// ✓ Literal object (op: "set" NÃO é operador de condition)
{ resource: "state", op: "set", path: "currentPlayer", value: "X" }

Isso permite que effect objects de handlers (que usam { resource, op: "set", path, value }) sejam processados corretamente sem serem confundidos com conditions.

TypeScript

import type {
  Expression,
  CompiledExpression,
  RefExpr,
  ConditionalExpr,
  FnExpr,
  PipeExpr,
  Scope,
  CompileOptions,
  AccessorFn,
} from '@statedelta-libs/expressions';

// Type guards
import {
  isRef,
  isConditional,
  isFn,
  isPipe,
  isCondition,
  isLiteral,
} from '@statedelta-libs/expressions';

Migração de v0.1.x

Breaking Changes

  1. Builtins removidos: Funções não são mais hardcoded. Passe via scope:

    // Antes
    compile({ $fn: "add", args: [1, 2] })
    
    // Depois
    compile({ $fn: "add", args: [1, 2] }, { scope: { add } })
  2. $fn sem args retorna função: Para chamar sem argumentos, use args: []:

    // Retorna a função
    { $fn: "sum" }
    
    // Chama a função
    { $fn: "getTime", args: [] }
  3. Removidos: registerFunction, hasFunction, getFunctionNames

  4. Novo: $pipe como sintaxe DSL para composição

Licença

MIT © Anderson D. Rosa