omni-ast
v2.0.2
Published
**JSON que executa código.**
Downloads
13
Readme
OMNI-AST
JSON que executa código.
Transformação bidirecional AST ↔ JSON ↔ Código com compactação inteligente.
Por que omni-ast?
O Problema
AST ESTree é verboso. Um simples { name: "João" } vira:
{
"type": "ObjectExpression",
"start": 0, "end": 19,
"properties": [{
"type": "Property",
"start": 2, "end": 17,
"key": { "type": "Identifier", "start": 2, "end": 6, "name": "name" },
"value": { "type": "Literal", "start": 8, "end": 17, "value": "João", "raw": "\"João\"" },
"kind": "init", "method": false, "shorthand": false, "computed": false
}]
}58 linhas de ruído para representar 1 propriedade.
A Solução
omni-ast compacta para:
{
"type": "JsonExpression",
"body": { "name": "João" }
}JSON puro. Sem ruído. Serializável. E o melhor: reconstruível de volta para código JS.
O Marcador #ast
O diferencial. Quando há lógica dinâmica, ela é isolada com #ast:
// Código original
{ name: "João", createdAt: Date.now() }{
"type": "JsonExpression",
"body": {
"name": "João",
"createdAt": {
"#ast": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": { "type": "Identifier", "name": "Date" },
"property": { "type": "Identifier", "name": "now" }
},
"arguments": []
}
}
}A Regra é Simples
#ast= porta de entrada pro mundo AST (valor é o type do nó)- Dentro do AST = usa
typenormal (ESTree) - Só expressões são permitidas (coisas que retornam valor)
// ✅ Válido - CallExpression é uma expressão
{ "#ast": "CallExpression", "callee": {...}, "arguments": [] }
// ❌ Inválido - IfStatement não retorna valor
{ "#ast": "IfStatement", "test": {...} }- Dados estáticos → JSON direto (zero overhead)
- Lógica executável → marcada com
#ast - 100% serializável → salva em banco, envia por API, armazena em arquivo
- 100% reconstruível → volta para código JavaScript
Instalação
pnpm add omni-astQuick Start
import { simplify, parseAST, generate, clearAST } from 'omni-ast';
import { parseScript } from 'meriyah'; // ou acorn, esprima, babel...
// 1. Código JavaScript
const code = '({ user: "Maria", total: calcular(items) })';
// 2. Parse + Normaliza + Simplifica
const json = simplify(clearAST(parseScript(code).body[0].expression));
// {
// type: "JsonExpression",
// body: {
// user: "Maria",
// total: { "#ast": "current", body: { type: "CallExpression", ... } }
// }
// }
// 3. Serializa (banco, API, arquivo...)
const stored = JSON.stringify(json);
// 4. Reconstrói
const ast = parseAST(JSON.parse(stored));
// 5. Gera código
generate(ast); // '{ user: "Maria", total: calcular(items) }'Fluxo
clearAST() simplify()
Código JavaScript ──────────────► AST Limpo ────────────► JSON Compacto
▲ │
│ parseAST() │
│ ◄─────────────────────────────────────────┘
│
│ generate()
└─────────────── AST| Função | Descrição |
|--------|-----------|
| normalize(ast) | Normaliza AST (remove metadados de parser) |
| simplify(ast) | Compacta para JSON com #ast |
| restore(json) | Reconstrói AST completo |
| generate(ast) | Gera código JavaScript |
Aliases deprecated:
clearAST→normalize,parseAST→restore
Casos de Uso
Configurações Declarativas com Lógica
import { json, ast, serialize } from 'omni-ast';
import * as b from 'omni-ast/builders';
const config = json({
apiUrl: "https://api.exemplo.com",
timeout: 5000,
getHeaders: ast(b.arrowFunctionExpression(
[],
b.objectExpression([
b.property(
b.identifier('Authorization'),
b.callExpression(b.identifier('getToken'), [])
)
])
))
});
serialize(config.body);
// '{ apiUrl: "https://api.exemplo.com", timeout: 5000, getHeaders: () => ({ Authorization: getToken() }) }'DSL Visual → Código Real
// Editor no-code salva definição como JSON
const definition = {
type: "JsonExpression",
body: {
action: "sendEmail",
to: { "#ast": "current", body: { type: "Identifier", name: "userEmail" } },
subject: "Bem-vindo!"
}
};
// Export para código executável
const code = generate(parseAST(definition));
// '{ action: "sendEmail", to: userEmail, subject: "Bem-vindo!" }'Workflows Declarativos
const workflow = json({
steps: [
{ action: "fetch", url: "https://api.com/data" },
{
action: "transform",
handler: ast(b.arrowFunctionExpression(
[b.identifier('data')],
b.callExpression(
b.memberExpression(b.identifier('data'), b.identifier('filter')),
[b.arrowFunctionExpression(
[b.identifier('x')],
b.memberExpression(b.identifier('x'), b.identifier('active'))
)]
)
))
},
{ action: "save", target: "database" }
]
});Validação e Análise de AST
import { walk, find, guards } from 'omni-ast';
// Encontra todas as chamadas de função
walk(ast, {
CallExpression(node) {
console.log('Chamada:', generate(node.callee));
}
});
// Busca específica
const asyncFn = find(ast, n =>
guards.isArrowFunctionExpression(n) && n.async
);API
Transformações
import { normalize, simplify, restore, generate, serialize } from 'omni-ast';
normalize(ast) // Normaliza (remove start, end, loc, range, raw, method)
simplify(ast) // AST → JSON compacto
restore(json) // JSON → AST completo
generate(ast) // AST → código JavaScript
serialize(json) // JSON híbrido → código (via #ast)Builders
47+ builders para construção programática:
import { builders as b } from 'omni-ast';
// const x = getValue()
b.variableDeclaration('const', [
b.variableDeclarator(
b.identifier('x'),
b.callExpression(b.identifier('getValue'), [])
)
]);| Categoria | Builders |
|-----------|----------|
| Básicos | identifier, literal |
| Expressões | callExpression, memberExpression, binaryExpression, conditionalExpression, arrowFunctionExpression, newExpression, awaitExpression |
| Coleções | arrayExpression, objectExpression, property, spreadElement, restElement |
| Statements | blockStatement, returnStatement, ifStatement, forStatement, whileStatement, tryStatement |
| Declarações | variableDeclaration, functionDeclaration |
| Especiais | ast, json, jsonExpression |
Type Guards
50+ guards com inferência TypeScript:
import { guards } from 'omni-ast';
if (guards.isCallExpression(node)) {
// TypeScript sabe: node é CallExpression
console.log(node.callee, node.arguments);
}Utilitários
import { walk, find, mutate } from 'omni-ast';
// Traversal
walk(ast, {
Identifier(node) { /* ... */ },
"*": (node) => { /* catch-all */ }
});
// Busca
const fn = find(ast, guards.isFunctionExpression);
// Transformação imutável
const newAst = mutate(ast,
n => n.type === 'Identifier' && n.name === 'foo',
n => ({ ...n, name: 'bar' })
);Parser-Agnostic
omni-ast funciona com qualquer parser ESTree. normalize() normaliza as diferenças:
import { parseScript } from 'meriyah'; // ⚡ Recomendado (menor, mais rápido)
import * as acorn from 'acorn';
import * as esprima from 'esprima';
// Todos funcionam:
normalize(parseScript(code));
normalize(acorn.parse(code, { ecmaVersion: 2022 }));
normalize(esprima.parseScript(code));| Parser | Bundle | Performance | |--------|--------|-------------| | Meriyah | ~12kb | ⚡ Mais rápido | | Acorn | ~13kb | Boa | | Esprima | ~20kb | Média | | Babel | Grande | TypeScript/JSX |
Zero Dependencies
omni-ast não tem dependências de runtime. Funciona standalone.
Decisões de Design do Generator
O generate() faz algumas escolhas intencionais para evitar ambiguidade no código gerado:
Parênteses em Arrow Functions
Arrow functions com body BinaryExpression, AssignmentExpression ou ObjectExpression recebem parênteses:
// Builder
b.arrowFunctionExpression([b.identifier('x')], b.binaryExpression('*', b.identifier('x'), b.literal(2)))
// Gerado: (x) => (x * 2)
// Não: (x) => x * 2Por quê? Evita ambiguidade em casos como:
// Sem parênteses, pode confundir
x => x + y * z // É (x + y) * z ou x + (y * z)?
// Com parênteses, fica claro
x => (x + y * z)IIFE com Arrow Functions
Quando uma arrow function é usada como callee de uma chamada (IIFE), ela é envolta em parênteses:
// Builder
b.callExpression(b.arrowFunctionExpression([], b.literal(42)), [])
// Gerado: (() => 42)()
// Não: () => 42() (que seria arrow retornando 42())Optional Chaining em Calls
O generator suporta optional chaining em chamadas:
// Builder
b.callExpression(b.identifier('fn'), [b.literal(1)], true)
// Gerado: fn?.(1)Documentação
Licença
MIT
