@statedelta-libs/expressions 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # @statedelta-libs/expressions
2
2
 
3
- > Compilador de JSON DSL para funções otimizadas.
3
+ > Compilador de JSON DSL puro para funções otimizadas.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@statedelta-libs/expressions.svg)](https://www.npmjs.com/package/@statedelta-libs/expressions)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
8
  ## Características
9
9
 
10
+ - **DSL Puro** - 100% JSON-serializável, sem callbacks inline
10
11
  - **Compilação** - Compila uma vez, executa milhões de vezes
11
12
  - **Alta performance** - ~25-30M ops/s após compilação
12
13
  - **Scope externo** - Funções vêm via scope, não hardcoded
@@ -15,7 +16,7 @@
15
16
  - **Path syntax** - Suporte a wildcards (`items[*].price`)
16
17
  - **Dependency extraction** - Para dirty tracking/reatividade
17
18
  - **Conditions nativo** - Condicionais integradas com expressions nos lados
18
- - **Custom Transforms** - Extensão do walker via transforms registrados (`$query`, `$mapper`, etc.)
19
+ - **normalize()** - Helper externo para custom transforms (`$query`, `$mapper`, etc.)
19
20
  - **Type-safe** - TypeScript nativo com inferência
20
21
 
21
22
  ## Instalação
@@ -29,18 +30,23 @@ pnpm add @statedelta-libs/expressions
29
30
  ## Quick Start
30
31
 
31
32
  ```typescript
32
- import { compile, evaluate } from '@statedelta-libs/expressions';
33
- import { filter, map, sum } from '@statedelta-libs/operators';
33
+ import { compile } from '@statedelta-libs/expressions';
34
34
 
35
35
  // Define o scope com funções disponíveis
36
- const scope = { filter, map, sum };
36
+ const scope = {
37
+ filter: (pred) => (arr) => arr?.filter(pred),
38
+ map: (fn) => (arr) => arr?.map(fn),
39
+ sum: (arr) => arr?.reduce((a, b) => a + b, 0),
40
+ isActive: (item) => item.active,
41
+ getPrice: (item) => item.price,
42
+ };
37
43
 
38
- // Compilar expressão com $pipe
44
+ // Compilar expressão com $pipe (DSL puro)
39
45
  const expr = compile({
40
46
  $pipe: [
41
47
  { $: "items" },
42
- { $fn: "filter", args: [(item) => item.active] },
43
- { $fn: "map", args: [(item) => item.price] },
48
+ { $fn: "filter", args: [{ $fn: "isActive" }] },
49
+ { $fn: "map", args: [{ $fn: "getPrice" }] },
44
50
  { $fn: "sum" }
45
51
  ]
46
52
  }, { scope });
@@ -115,16 +121,6 @@ const { fn } = compileAST(
115
121
  fn(); // usa ctx.get("hp") + ctx.get("mp")
116
122
  ```
117
123
 
118
- Código gerado:
119
- ```js
120
- (function(scope){
121
- const{add,accessor}=scope;
122
- return function(data){
123
- return add(accessor("hp",data),accessor("mp",data))
124
- }
125
- })
126
- ```
127
-
128
124
  #### lexicalPrefix (compileAST)
129
125
 
130
126
  Para máxima performance em paths específicos, use `lexicalPrefix` para acesso direto via destructuring:
@@ -147,22 +143,6 @@ const { fn } = compileAST(
147
143
  fn({ params: { damage: 25 } }); // ctx.get("hp") + params.damage
148
144
  ```
149
145
 
150
- Código gerado:
151
- ```js
152
- (function(scope){
153
- const{add,accessor}=scope;
154
- return function(data){
155
- const{params}=data??{}; // destructuring direto (rápido!)
156
- return add(accessor("hp",data),params?.damage)
157
- }
158
- })
159
- ```
160
-
161
- | Path | Com lexicalPrefix | Sem lexicalPrefix |
162
- |------|-------------------|-------------------|
163
- | `params.damage` | `params?.damage` (direto) | `accessor("params.damage", data)` |
164
- | `hp` | `accessor("hp", data)` | `accessor("hp", data)` |
165
-
166
146
  ### evaluate() / evaluateAST()
167
147
 
168
148
  Compila e executa em um passo.
@@ -192,32 +172,30 @@ const deps = extractDeps({
192
172
  // ["$isVip", "price.vip", "price.regular"]
193
173
  ```
194
174
 
195
- ## JSON Aninhado (Walk Automático)
175
+ ### normalize()
196
176
 
197
- O compilador **percorre automaticamente** todo o JSON e resolve expressões onde quer que estejam:
177
+ Normaliza expressões customizadas para DSL puro **antes** da compilação.
198
178
 
199
179
  ```typescript
200
- const { fn } = compile({
201
- name: "John",
202
- damage: { $: "params.damage" },
203
- active: { $if: "hp.current", then: true, else: false },
204
- nested: {
205
- deep: {
206
- value: { $fn: "add", args: [{ $: "a" }, { $: "b" }] }
207
- }
208
- }
209
- }, { scope });
180
+ import { normalize, type Transforms } from '@statedelta-libs/expressions';
210
181
 
211
- fn({ params: { damage: 25 }, hp: { current: 100 }, a: 1, b: 2 });
212
- // {
213
- // name: "John",
214
- // damage: 25,
215
- // active: true,
216
- // nested: { deep: { value: 3 } }
217
- // }
218
- ```
182
+ const transforms: Transforms = {
183
+ $query: (node) => ({
184
+ $fn: "__query",
185
+ args: [node.$query as string, (node.params ?? {}) as Expression],
186
+ }),
187
+ };
219
188
 
220
- Não precisa de walker manual - o `compile()` e `compileAST()` já fazem isso internamente.
189
+ // Converte expressão customizada para DSL puro
190
+ const pure = normalize(
191
+ { $query: "isAttacked", params: { row: { $: "kingRow" } } },
192
+ transforms
193
+ );
194
+ // → { $fn: "__query", args: ["isAttacked", { row: { $: "kingRow" } }] }
195
+
196
+ // Agora pode compilar normalmente
197
+ const { fn } = compile(pure, { scope });
198
+ ```
221
199
 
222
200
  ## Tipos de Expressão
223
201
 
@@ -260,10 +238,10 @@ Composição com valor inicial - o valor passa por cada função em sequência.
260
238
  ```typescript
261
239
  {
262
240
  $pipe: [
263
- { $: "items" }, // Valor inicial
264
- { $fn: "filter", args: [predicateFn] }, // Retorna função curried
265
- { $fn: "map", args: [mapperFn] }, // Retorna função curried
266
- { $fn: "sum" } // Referência à função
241
+ { $: "items" }, // Valor inicial
242
+ { $fn: "filter", args: [{ $fn: "isActive" }] }, // Curried filter
243
+ { $fn: "map", args: [{ $fn: "getPrice" }] }, // Curried map
244
+ { $fn: "sum" } // Referência à função
267
245
  ]
268
246
  }
269
247
  ```
@@ -284,6 +262,10 @@ Chama função do scope com argumentos compilados.
284
262
  // Com args vazio: chama função sem argumentos
285
263
  { $fn: "getTimestamp", args: [] }
286
264
  // → scope.getTimestamp()
265
+
266
+ // Passando função como argumento (referência via $fn)
267
+ { $fn: "filter", args: [{ $fn: "isActive" }] }
268
+ // → scope.filter(scope.isActive)
287
269
  ```
288
270
 
289
271
  ### Condition
@@ -315,8 +297,6 @@ Condicionais compiladas internamente. Ambos os lados aceitam qualquer expressão
315
297
 
316
298
  **Operadores:** `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`, `contains`, `notContains`, `exists`, `notExists`, `matches`, `notMatches`, `startsWith`, `endsWith`
317
299
 
318
- > **Nota:** Objetos com `op: "set"` ou outros operadores não-condition são tratados como literals.
319
-
320
300
  ## Scope
321
301
 
322
302
  O scope define quais funções estão disponíveis para `$fn`:
@@ -333,383 +313,115 @@ const scope = {
333
313
  filter: R.filter,
334
314
  map: R.map,
335
315
  sum: R.sum,
336
- // Funções custom
316
+ // Funções custom (predicados, mappers, etc.)
337
317
  double: (n) => n * 2,
338
- isActive: (item) => item.active
318
+ isActive: (item) => item.active,
319
+ getPrice: (item) => item.price,
339
320
  };
340
321
 
341
322
  const { fn } = compile(expression, { scope });
342
323
  ```
343
324
 
344
- ## Accessor Customizado
345
-
346
- Para objetos que usam interface de acesso customizada (como `ctx.get(path)`), use o `accessor`:
347
-
348
- ```typescript
349
- // Contexto com método get() customizado
350
- interface TickContext {
351
- get(path: string): unknown;
352
- }
353
-
354
- const accessor = (path: string, ctx: TickContext) => ctx.get(path);
355
-
356
- // Compilação com accessor
357
- const { fn } = compile<TickContext>(
358
- { $: "hp:value" },
359
- { accessor }
360
- );
361
-
362
- fn(tickContext); // usa ctx.get('hp:value')
363
-
364
- // Com scope + accessor
365
- const { fn } = compile<TickContext>(
366
- { $fn: "add", args: [{ $: "a" }, { $: "b" }] },
367
- { scope, accessor }
368
- );
369
- ```
370
-
371
- O accessor é propagado para:
372
- - Referências (`{ $: "path" }`)
373
- - Shorthand de `$if` (`{ $if: "path", ... }`)
374
- - Argumentos de `$fn`
375
- - Steps de `$pipe`
376
- - Conditions (compiladas internamente)
377
-
378
- **Performance:** Igual ao acesso direto (~25-30M ops/s).
379
-
380
- ## Path Syntax
381
-
382
- | Path | Resultado |
383
- |------|-----------|
384
- | `user` | `data.user` |
385
- | `user.name` | `data.user.name` |
386
- | `items[0]` | `data.items[0]` |
387
- | `items[*].price` | `data.items.map(i => i.price)` |
388
-
389
- ## Cache
390
-
391
- ```typescript
392
- import { cache, cached } from '@statedelta-libs/expressions';
393
-
394
- // Com scope
395
- const compiled = cache.get(expression, { scope });
396
-
397
- // Ou função helper
398
- const compiled = cached(expression, { scope });
399
-
400
- // Gerenciar
401
- cache.size; // Tamanho atual
402
- cache.clear(); // Limpar
403
- ```
404
-
405
- ## Performance
406
-
407
- ### compile() - Closures
408
-
409
- | Operação | Velocidade |
410
- |----------|------------|
411
- | Compile (literal) | ~6.5M ops/s |
412
- | Compile (ref) | ~3M ops/s |
413
- | Compile (fn nested) | ~550K ops/s |
414
- | Compile (complex) | ~300K ops/s |
415
- | Execute (ref) | ~28-30M ops/s |
416
- | Execute (fn nested) | ~9M ops/s |
417
- | Execute (com accessor) | ~23-30M ops/s |
418
-
419
- ### compileAST() - AST + new Function
420
-
421
- | Operação | Velocidade |
422
- |----------|------------|
423
- | Compile (literal) | ~1.2M ops/s |
424
- | Compile (ref) | ~800K ops/s |
425
- | Compile (fn nested) | ~230K ops/s |
426
- | Compile (complex) | ~130K ops/s |
427
- | Execute (ref) | ~27M ops/s |
428
- | Execute (fn nested) | ~25M ops/s ⚡ |
429
-
430
- ### Comparação Execução
431
-
432
- | Cenário | Closures | AST | Ganho AST |
433
- |---------|----------|-----|-----------|
434
- | fn nested | 9M ops/s | 25M ops/s | **+169%** |
435
- | ref 4 níveis | 16M ops/s | 27M ops/s | **+69%** |
436
- | conditional nested | 20M ops/s | 25M ops/s | **+25%** |
325
+ ## Custom Transforms (normalize)
437
326
 
438
- **Break-even:** ~8 execuções - após isso, `compileAST()` compensa o tempo extra de compilação.
439
-
440
- ## Segurança
441
-
442
- Ambas as abordagens são **seguras contra injeção de código**:
443
-
444
- | Método | Proteção |
445
- |--------|----------|
446
- | `compile()` | Não gera código string - apenas compõe funções |
447
- | `compileAST()` | Usa destructuring que valida identificadores automaticamente |
327
+ Para tipos de expressão customizados (`$query`, `$mapper`, etc.), use `normalize()` para convertê-los em DSL puro **antes** da compilação:
448
328
 
449
329
  ```typescript
450
- // Tentativa de injeção - FALHA em ambos
451
- { $fn: "add; console.log('hacked'); //" }
452
-
453
- // compile(): tenta acessar scope["add; console.log..."] → undefined
454
- // compileAST(): destructuring inválido → SyntaxError
455
- ```
456
-
457
- Funções só são acessíveis se existirem no `scope` fornecido pelo desenvolvedor.
458
-
459
- ## Custom Transforms
460
-
461
- Mecanismo de extensão do walker para tipos de expressão customizados. Permite registrar transforms que interceptam objetos com chaves específicas e os convertem em expressões padrão antes da compilação.
330
+ import { normalize, compile, type Transforms, type Expression } from '@statedelta-libs/expressions';
462
331
 
463
- ### API
464
-
465
- ```typescript
466
- const transforms = {
332
+ // Define transforms
333
+ const transforms: Transforms = {
467
334
  $query: (node) => ({
468
335
  $fn: "__query",
469
- args: [node.$query, node.params ?? {}]
336
+ args: [node.$query as string, (node.params ?? {}) as Expression],
470
337
  }),
471
338
  $mapper: (node) => ({
472
339
  $fn: "__mapper",
473
- args: [node.$mapper, node.params ?? {}]
340
+ args: [node.$mapper as string],
474
341
  }),
475
342
  };
476
- ```
477
-
478
- ### Uso com compile() e compileAST()
479
-
480
- ```typescript
481
- import { compile, compileAST } from '@statedelta-libs/expressions';
482
343
 
344
+ // Define scope com as funções de runtime
483
345
  const scope = {
484
346
  __query: (name, params) => queryEngine.run(name, params),
347
+ __mapper: (name) => mapperRegistry.get(name),
485
348
  };
486
349
 
487
- // Closures
488
- const { fn } = compile(
489
- { $query: "isKingAttacked", params: { row: 0, col: 4 } },
490
- { scope, transforms }
491
- );
492
-
493
- // AST (mesmo contrato)
494
- const { fn } = compileAST(
495
- { $query: "isKingAttacked", params: { row: 0, col: 4 } },
496
- { scope, transforms }
350
+ // 1. Normalize (converte para DSL puro)
351
+ const pure = normalize(
352
+ { $query: "isAttacked", params: { row: { $: "kingRow" } } },
353
+ transforms
497
354
  );
498
- ```
499
-
500
- ### Expressions dentro de params
501
355
 
502
- Os params do transform são compilados recursivamente — referências e expressões dentro deles são resolvidas normalmente:
503
-
504
- ```typescript
505
- const { fn } = compile(
506
- { $query: "check", params: { row: { $: "myRow" }, col: { $: "myCol" } } },
507
- { scope, transforms }
508
- );
356
+ // 2. Compile (DSL puro)
357
+ const { fn } = compile(pure, { scope });
509
358
 
510
- fn({ myRow: 5, myCol: 3 });
511
- // → __query("check", { row: 5, col: 3 })
359
+ // 3. Execute
360
+ fn({ kingRow: 0 });
512
361
  ```
513
362
 
514
363
  ### Aninhamento
515
364
 
516
- Transforms funcionam em qualquer posição dentro de `$if`, `$pipe`, `$fn`, conditions, plain objects:
365
+ O `normalize` percorre toda a árvore, então transforms funcionam em qualquer posição:
517
366
 
518
367
  ```typescript
519
- // Dentro de condition
520
- {
521
- $if: {
522
- left: { $query: "isAttacked", params: { row: 0 } },
523
- op: "eq",
524
- right: true
368
+ normalize(
369
+ {
370
+ $if: {
371
+ left: { $query: "isAttacked", params: { row: 0 } },
372
+ op: "eq",
373
+ right: true
374
+ },
375
+ then: "check",
376
+ else: "safe"
525
377
  },
526
- then: "check",
527
- else: "safe"
528
- }
529
-
530
- // Dentro de plain object (walk automático)
531
- {
532
- damage: { $query: "getDamage", params: { base: 10 } },
533
- name: "attack"
534
- }
535
- ```
536
-
537
- ### Extração de deps
538
-
539
- `extractDeps` também resolve transforms para capturar dependências internas:
540
-
541
- ```typescript
542
- import { extractDeps } from '@statedelta-libs/expressions';
543
-
544
- const deps = extractDeps(
545
- { $query: "check", params: { row: { $: "myRow" } } },
546
378
  transforms
547
379
  );
548
- // ["myRow"]
549
380
  ```
550
381
 
551
- ### Proteção contra loops infinitos
382
+ ### Proteção contra loops
552
383
 
553
- Se um transform retornar um objeto com a mesma chave de entrada, um erro é lançado:
384
+ Se um transform retornar objeto com a mesma chave, erro é lançado:
554
385
 
555
386
  ```typescript
556
387
  const badTransforms = {
557
388
  $loop: (node) => ({ $loop: node.$loop }), // mesma chave!
558
389
  };
559
390
 
560
- compile({ $loop: "test" }, { transforms: badTransforms });
391
+ normalize({ $loop: "test" }, badTransforms);
561
392
  // Error: Transform "$loop" returned object with same key — infinite loop
562
393
  ```
563
394
 
564
- ### Performance
565
-
566
- Transforms são resolvidos **em tempo de compilação**. Zero overhead em runtime — a função compilada resultante é idêntica a uma expressão `$fn` normal.
567
-
568
- ## Type Detection
569
-
570
- O compilador detecta automaticamente o tipo de expressão baseado na estrutura do objeto:
571
-
572
- | Tipo | Detecção | Exemplo |
573
- |------|----------|---------|
574
- | Reference | `{ $ }` presente | `{ $: "user.name" }` |
575
- | Conditional | `{ $if, then }` presentes | `{ $if: cond, then: x, else: y }` |
576
- | Function | `{ $fn }` presente | `{ $fn: "add", args: [...] }` |
577
- | Pipe | `{ $pipe }` presente | `{ $pipe: [...] }` |
578
- | Condition | `{ left, op }` com operador válido | `{ left: { $: "age" }, op: "gte", right: 18 }` |
579
- | Custom Transform | Chave registrada em `transforms` | `{ $query: "check", params: {...} }` |
580
- | Literal | Nenhum dos acima | `{ foo: "bar" }`, `42`, `"hello"` |
581
-
582
- ### Distinção entre Conditions e Effect Objects
583
-
584
- Objetos com `left` e `op` só são tratados como conditions se `op` for um operador válido:
585
-
586
- ```typescript
587
- // ✓ Condition (op: "eq" é operador válido)
588
- { left: { $: "user.age" }, op: "eq", right: 18 }
589
-
590
- // ✓ Literal object (op: "set" NÃO é operador de condition)
591
- { resource: "state", op: "set", path: "currentPlayer", value: "X" }
592
- ```
593
-
594
- Isso permite que effect objects de handlers (que usam `{ resource, op: "set", path, value }`) sejam processados corretamente sem serem confundidos com conditions.
595
-
596
395
  ## Builders
597
396
 
598
- Funções declarativas para construir expressões. Disponíveis via namespace `builders`:
397
+ Funções declarativas para construir expressões:
599
398
 
600
399
  ```typescript
601
- import { builders, compile } from '@statedelta-libs/expressions';
400
+ import { builders } from '@statedelta-libs/expressions';
602
401
 
603
402
  const { $, $fn, $if, $pipe, $cond } = builders;
604
- ```
605
-
606
- ### `$` / `ref` - Path Reference
607
-
608
- ```typescript
609
- $("player.hp") // { $: "player.hp" }
610
- $("items[0].price") // { $: "items[0].price" }
611
- $("items[*].price") // { $: "items[*].price" } (wildcard)
612
- ```
613
-
614
- ### `$fn` / `fn` - Function Call
615
403
 
616
- ```typescript
617
- // Com argumentos - chama a função
618
- $fn("add", [$("a"), $("b")]) // { $fn: "add", args: [...] }
619
- $fn("multiply", [10, $("x")]) // literal + expression
404
+ // Path reference
405
+ $("player.hp") // { $: "player.hp" }
620
406
 
621
- // Sem argumentos - retorna referência à função (útil em $pipe)
407
+ // Function call
408
+ $fn("add", [$("a"), $("b")]) // { $fn: "add", args: [...] }
622
409
  $fn("sum") // { $fn: "sum" }
623
410
 
624
- // Com callbacks (predicados, mappers)
625
- $fn("filter", [(item) => item.active])
626
- $fn("map", [(item) => item.price])
627
- ```
628
-
629
- ### `$if` - Conditional
630
-
631
- ```typescript
632
- // Com else
633
- $if($("isVip"), 0.2, 0)
634
- // { $if: { $: "isVip" }, then: 0.2, else: 0 }
635
-
636
- // Sem else
637
- $if($("active"), "yes")
638
- // { $if: { $: "active" }, then: "yes" }
639
-
640
- // Nested
641
- $if($("level1"), $if($("level2"), "deep", "shallow"), "none")
642
- ```
643
-
644
- ### `$pipe` / `pipe` - Composition
411
+ // Conditional
412
+ $if($("isVip"), 0.2, 0) // { $if: ..., then: 0.2, else: 0 }
645
413
 
646
- ```typescript
414
+ // Pipe
647
415
  $pipe(
648
- $("items"), // valor inicial
649
- $fn("filter", [(x) => x.active]), // transforma
650
- $fn("map", [(x) => x.price]), // transforma
651
- $fn("sum") // resultado
652
- )
653
- ```
654
-
655
- ### `$cond` / `cond` - Condition
656
-
657
- ```typescript
658
- // Básico
659
- $cond($("age"), "gte", 18)
660
- // { left: { $: "age" }, op: "gte", right: 18 }
661
-
662
- // Exists (sem right)
663
- $cond($("email"), "exists")
664
-
665
- // Ref vs Ref
666
- $cond($("price"), "lte", $("budget"))
667
-
668
- // Com $fn nos lados
669
- $cond($fn("add", [$("a"), $("b")]), "gt", 100)
670
-
671
- // Com $pipe
672
- $cond($pipe($("name"), $fn("upper")), "eq", "ADMIN")
673
- ```
674
-
675
- ### Exemplo Completo
676
-
677
- ```typescript
678
- import { builders, compile } from '@statedelta-libs/expressions';
679
- import { filter, map, sum } from '@statedelta-libs/operators';
680
-
681
- const { $, $fn, $if, $pipe, $cond } = builders;
682
-
683
- const scope = { filter, map, sum };
684
-
685
- // Soma preços de itens ativos com desconto VIP
686
- const expr = $pipe(
687
416
  $("items"),
688
- $fn("filter", [(item) => item.active]),
689
- $fn("map", [(item) => item.price]),
417
+ $fn("filter", [$fn("isActive")]),
690
418
  $fn("sum")
691
- );
692
-
693
- const { fn } = compile(expr, { scope });
419
+ )
694
420
 
695
- fn({
696
- items: [
697
- { price: 10, active: true },
698
- { price: 20, active: false },
699
- { price: 30, active: true }
700
- ]
701
- }); // 40
421
+ // Condition
422
+ $cond($("age"), "gte", 18) // { left: ..., op: "gte", right: 18 }
702
423
  ```
703
424
 
704
- ### Aliases
705
-
706
- | Builder | Alias |
707
- |---------|-------|
708
- | `$` | `ref` |
709
- | `$fn` | `fn` |
710
- | `$pipe` | `pipe` |
711
- | `$cond` | `cond` |
712
-
713
425
  ## TypeScript
714
426
 
715
427
  ```typescript
@@ -728,6 +440,7 @@ import type {
728
440
  CompileOptions,
729
441
  AccessorFn,
730
442
  TransformFn,
443
+ Transforms,
731
444
  } from '@statedelta-libs/expressions';
732
445
 
733
446
  // Type guards
@@ -741,64 +454,56 @@ import {
741
454
  } from '@statedelta-libs/expressions';
742
455
  ```
743
456
 
744
- ## Migração de v0.1.x
745
-
746
- ### Breaking Changes
457
+ ## Performance
747
458
 
748
- 1. **Builtins removidos**: Funções não são mais hardcoded. Passe via `scope`:
749
- ```typescript
750
- // Antes
751
- compile({ $fn: "add", args: [1, 2] })
459
+ ### Comparação Execução
752
460
 
753
- // Depois
754
- compile({ $fn: "add", args: [1, 2] }, { scope: { add } })
755
- ```
461
+ | Cenário | Closures | AST | Ganho AST |
462
+ |---------|----------|-----|-----------|
463
+ | fn nested | 9M ops/s | 25M ops/s | **+169%** |
464
+ | ref 4 níveis | 16M ops/s | 27M ops/s | **+69%** |
465
+ | conditional nested | 20M ops/s | 25M ops/s | **+25%** |
756
466
 
757
- 2. **$fn sem args retorna função**: Para chamar sem argumentos, use `args: []`:
758
- ```typescript
759
- // Retorna a função
760
- { $fn: "sum" }
467
+ **Break-even:** ~8 execuções - após isso, `compileAST()` compensa o tempo extra de compilação.
761
468
 
762
- // Chama a função
763
- { $fn: "getTime", args: [] }
764
- ```
469
+ ## Segurança
765
470
 
766
- 3. **Removidos**: `registerFunction`, `hasFunction`, `getFunctionNames`
471
+ Ambas as abordagens são **seguras contra injeção de código**:
767
472
 
768
- 4. **Novo**: `$pipe` como sintaxe DSL para composição
473
+ | Método | Proteção |
474
+ |--------|----------|
475
+ | `compile()` | Não gera código string - apenas compõe funções |
476
+ | `compileAST()` | Usa destructuring que valida identificadores automaticamente |
769
477
 
770
- ### v1.0 v1.1
478
+ Funções são acessíveis se existirem no `scope` fornecido pelo desenvolvedor.
771
479
 
772
- 1. **Conditions internas**: `@statedelta-libs/conditions` não é mais dependência. Conditions são compiladas internamente.
480
+ ## Migração
773
481
 
774
- 2. **Expressions nos lados**: `left`/`right` de conditions agora aceitam qualquer `Expression` (`$fn`, `$pipe`, `$if`, etc), não apenas `Ref | literal`.
482
+ ### v1.1 v1.2
775
483
 
776
- 3. **Novo builder**: `$cond(left, op, right?)` para construir conditions com expressions:
484
+ 1. **DSL Puro**: `FnExpr.args` agora é `Expression[]` (removido suporte a callbacks inline). Funções devem ser passadas via scope:
777
485
  ```typescript
778
- $cond($fn("add", [$("a"), $("b")]), "gt", 100)
779
- ```
486
+ // Antes (callbacks inline - não funciona mais)
487
+ { $fn: "filter", args: [(item) => item.active] }
780
488
 
781
- 4. **Novos tipos**: `ConditionOp`, `Condition`, `ConditionGroup`, `ConditionExpr` exportados diretamente do expressions.
489
+ // Depois (referência via scope)
490
+ { $fn: "filter", args: [{ $fn: "isActive" }] }
491
+ // Com scope: { isActive: (item) => item.active, filter: ... }
492
+ ```
782
493
 
783
- ### v1.1 v1.2
494
+ 2. **Removidos**: `CallbackFn`, `FnArg` types
784
495
 
785
- 1. **Custom Transforms**: Nova option `transforms` em `CompileOptions` e `CompileASTOptions`. Permite estender o walker com tipos de expressão customizados (`$query`, `$mapper`, etc.):
496
+ 3. **normalize()**: Custom transforms agora são externos via `normalize()`, não mais dentro de `CompileOptions`:
786
497
  ```typescript
787
- const transforms = {
788
- $query: (node) => ({ $fn: "__query", args: [node.$query, node.params ?? {}] }),
789
- };
498
+ // Antes
790
499
  compile(expr, { scope, transforms });
791
- compileAST(expr, { scope, transforms });
792
- ```
793
500
 
794
- 2. **extractDeps com transforms**: `extractDeps` agora aceita `transforms` como segundo argumento para capturar deps dentro de expressões customizadas:
795
- ```typescript
796
- extractDeps(expr, transforms);
501
+ // Depois
502
+ const pure = normalize(expr, transforms);
503
+ compile(pure, { scope });
797
504
  ```
798
505
 
799
- 3. **Novo tipo**: `TransformFn` exportado diretamente do expressions.
800
-
801
- 4. **Zero breaking change**: O campo `transforms` é opcional. Sem ele, o comportamento é idêntico ao anterior.
506
+ 4. **Novos tipos**: `Transforms` (alias para `Record<string, TransformFn>`)
802
507
 
803
508
  ## Licença
804
509