@statedelta-libs/expressions 1.2.1 → 2.0.1

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
+ };
188
+
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" } }] }
219
195
 
220
- Não precisa de walker manual - o `compile()` e `compileAST()` já fazem isso internamente.
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,388 +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).
325
+ ## Custom Transforms (normalize)
379
326
 
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
327
+ Para tipos de expressão customizados (`$query`, `$mapper`, etc.), use `normalize()` para convertê-los em DSL puro **antes** da compilação:
390
328
 
391
329
  ```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
- ```
330
+ import { normalize, compile, type Transforms, type Expression } from '@statedelta-libs/expressions';
404
331
 
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%** |
437
-
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 |
448
-
449
- ```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.
462
-
463
- ### API
464
-
465
- Transforms retornam `Expression` ou `TransformResult`. Use `TransformResult` quando os args contêm tipos domain-specific (não `FnArg`):
466
-
467
- ```typescript
468
- import type { TransformFn, TransformResult } from '@statedelta-libs/expressions';
469
-
470
- const transforms: Record<string, TransformFn> = {
471
- // TransformResult.args aceita unknown[] — sem casts necessários
332
+ // Define transforms
333
+ const transforms: Transforms = {
472
334
  $query: (node) => ({
473
335
  $fn: "__query",
474
- args: [node.$query, node.params ?? {}]
336
+ args: [node.$query as string, (node.params ?? {}) as Expression],
475
337
  }),
476
- $test: (node) => ({
477
- $fn: "__test",
478
- args: [node.$test] // TestSpec, QueryParams, etc. — qualquer tipo
338
+ $mapper: (node) => ({
339
+ $fn: "__mapper",
340
+ args: [node.$mapper as string],
479
341
  }),
480
342
  };
481
- ```
482
-
483
- ### Uso com compile() e compileAST()
484
-
485
- ```typescript
486
- import { compile, compileAST } from '@statedelta-libs/expressions';
487
343
 
344
+ // Define scope com as funções de runtime
488
345
  const scope = {
489
346
  __query: (name, params) => queryEngine.run(name, params),
347
+ __mapper: (name) => mapperRegistry.get(name),
490
348
  };
491
349
 
492
- // Closures
493
- const { fn } = compile(
494
- { $query: "isKingAttacked", params: { row: 0, col: 4 } },
495
- { scope, transforms }
350
+ // 1. Normalize (converte para DSL puro)
351
+ const pure = normalize(
352
+ { $query: "isAttacked", params: { row: { $: "kingRow" } } },
353
+ transforms
496
354
  );
497
355
 
498
- // AST (mesmo contrato)
499
- const { fn } = compileAST(
500
- { $query: "isKingAttacked", params: { row: 0, col: 4 } },
501
- { scope, transforms }
502
- );
503
- ```
356
+ // 2. Compile (DSL puro)
357
+ const { fn } = compile(pure, { scope });
504
358
 
505
- ### Expressions dentro de params
506
-
507
- Os params do transform são compilados recursivamente — referências e expressões dentro deles são resolvidas normalmente:
508
-
509
- ```typescript
510
- const { fn } = compile(
511
- { $query: "check", params: { row: { $: "myRow" }, col: { $: "myCol" } } },
512
- { scope, transforms }
513
- );
514
-
515
- fn({ myRow: 5, myCol: 3 });
516
- // → __query("check", { row: 5, col: 3 })
359
+ // 3. Execute
360
+ fn({ kingRow: 0 });
517
361
  ```
518
362
 
519
363
  ### Aninhamento
520
364
 
521
- 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:
522
366
 
523
367
  ```typescript
524
- // Dentro de condition
525
- {
526
- $if: {
527
- left: { $query: "isAttacked", params: { row: 0 } },
528
- op: "eq",
529
- 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"
530
377
  },
531
- then: "check",
532
- else: "safe"
533
- }
534
-
535
- // Dentro de plain object (walk automático)
536
- {
537
- damage: { $query: "getDamage", params: { base: 10 } },
538
- name: "attack"
539
- }
540
- ```
541
-
542
- ### Extração de deps
543
-
544
- `extractDeps` também resolve transforms para capturar dependências internas:
545
-
546
- ```typescript
547
- import { extractDeps } from '@statedelta-libs/expressions';
548
-
549
- const deps = extractDeps(
550
- { $query: "check", params: { row: { $: "myRow" } } },
551
378
  transforms
552
379
  );
553
- // ["myRow"]
554
380
  ```
555
381
 
556
- ### Proteção contra loops infinitos
382
+ ### Proteção contra loops
557
383
 
558
- 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:
559
385
 
560
386
  ```typescript
561
387
  const badTransforms = {
562
388
  $loop: (node) => ({ $loop: node.$loop }), // mesma chave!
563
389
  };
564
390
 
565
- compile({ $loop: "test" }, { transforms: badTransforms });
391
+ normalize({ $loop: "test" }, badTransforms);
566
392
  // Error: Transform "$loop" returned object with same key — infinite loop
567
393
  ```
568
394
 
569
- ### Performance
570
-
571
- 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.
572
-
573
- ## Type Detection
574
-
575
- O compilador detecta automaticamente o tipo de expressão baseado na estrutura do objeto:
576
-
577
- | Tipo | Detecção | Exemplo |
578
- |------|----------|---------|
579
- | Reference | `{ $ }` presente | `{ $: "user.name" }` |
580
- | Conditional | `{ $if, then }` presentes | `{ $if: cond, then: x, else: y }` |
581
- | Function | `{ $fn }` presente | `{ $fn: "add", args: [...] }` |
582
- | Pipe | `{ $pipe }` presente | `{ $pipe: [...] }` |
583
- | Condition | `{ left, op }` com operador válido | `{ left: { $: "age" }, op: "gte", right: 18 }` |
584
- | Custom Transform | Chave registrada em `transforms` | `{ $query: "check", params: {...} }` |
585
- | Literal | Nenhum dos acima | `{ foo: "bar" }`, `42`, `"hello"` |
586
-
587
- ### Distinção entre Conditions e Effect Objects
588
-
589
- Objetos com `left` e `op` só são tratados como conditions se `op` for um operador válido:
590
-
591
- ```typescript
592
- // ✓ Condition (op: "eq" é operador válido)
593
- { left: { $: "user.age" }, op: "eq", right: 18 }
594
-
595
- // ✓ Literal object (op: "set" NÃO é operador de condition)
596
- { resource: "state", op: "set", path: "currentPlayer", value: "X" }
597
- ```
598
-
599
- Isso permite que effect objects de handlers (que usam `{ resource, op: "set", path, value }`) sejam processados corretamente sem serem confundidos com conditions.
600
-
601
395
  ## Builders
602
396
 
603
- Funções declarativas para construir expressões. Disponíveis via namespace `builders`:
397
+ Funções declarativas para construir expressões:
604
398
 
605
399
  ```typescript
606
- import { builders, compile } from '@statedelta-libs/expressions';
400
+ import { builders } from '@statedelta-libs/expressions';
607
401
 
608
402
  const { $, $fn, $if, $pipe, $cond } = builders;
609
- ```
610
-
611
- ### `$` / `ref` - Path Reference
612
-
613
- ```typescript
614
- $("player.hp") // { $: "player.hp" }
615
- $("items[0].price") // { $: "items[0].price" }
616
- $("items[*].price") // { $: "items[*].price" } (wildcard)
617
- ```
618
403
 
619
- ### `$fn` / `fn` - Function Call
404
+ // Path reference
405
+ $("player.hp") // { $: "player.hp" }
620
406
 
621
- ```typescript
622
- // Com argumentos - chama a função
623
- $fn("add", [$("a"), $("b")]) // { $fn: "add", args: [...] }
624
- $fn("multiply", [10, $("x")]) // literal + expression
625
-
626
- // Sem argumentos - retorna referência à função (útil em $pipe)
407
+ // Function call
408
+ $fn("add", [$("a"), $("b")]) // { $fn: "add", args: [...] }
627
409
  $fn("sum") // { $fn: "sum" }
628
410
 
629
- // Com callbacks (predicados, mappers)
630
- $fn("filter", [(item) => item.active])
631
- $fn("map", [(item) => item.price])
632
- ```
633
-
634
- ### `$if` - Conditional
635
-
636
- ```typescript
637
- // Com else
638
- $if($("isVip"), 0.2, 0)
639
- // { $if: { $: "isVip" }, then: 0.2, else: 0 }
640
-
641
- // Sem else
642
- $if($("active"), "yes")
643
- // { $if: { $: "active" }, then: "yes" }
644
-
645
- // Nested
646
- $if($("level1"), $if($("level2"), "deep", "shallow"), "none")
647
- ```
648
-
649
- ### `$pipe` / `pipe` - Composition
411
+ // Conditional
412
+ $if($("isVip"), 0.2, 0) // { $if: ..., then: 0.2, else: 0 }
650
413
 
651
- ```typescript
414
+ // Pipe
652
415
  $pipe(
653
- $("items"), // valor inicial
654
- $fn("filter", [(x) => x.active]), // transforma
655
- $fn("map", [(x) => x.price]), // transforma
656
- $fn("sum") // resultado
657
- )
658
- ```
659
-
660
- ### `$cond` / `cond` - Condition
661
-
662
- ```typescript
663
- // Básico
664
- $cond($("age"), "gte", 18)
665
- // { left: { $: "age" }, op: "gte", right: 18 }
666
-
667
- // Exists (sem right)
668
- $cond($("email"), "exists")
669
-
670
- // Ref vs Ref
671
- $cond($("price"), "lte", $("budget"))
672
-
673
- // Com $fn nos lados
674
- $cond($fn("add", [$("a"), $("b")]), "gt", 100)
675
-
676
- // Com $pipe
677
- $cond($pipe($("name"), $fn("upper")), "eq", "ADMIN")
678
- ```
679
-
680
- ### Exemplo Completo
681
-
682
- ```typescript
683
- import { builders, compile } from '@statedelta-libs/expressions';
684
- import { filter, map, sum } from '@statedelta-libs/operators';
685
-
686
- const { $, $fn, $if, $pipe, $cond } = builders;
687
-
688
- const scope = { filter, map, sum };
689
-
690
- // Soma preços de itens ativos com desconto VIP
691
- const expr = $pipe(
692
416
  $("items"),
693
- $fn("filter", [(item) => item.active]),
694
- $fn("map", [(item) => item.price]),
417
+ $fn("filter", [$fn("isActive")]),
695
418
  $fn("sum")
696
- );
697
-
698
- const { fn } = compile(expr, { scope });
419
+ )
699
420
 
700
- fn({
701
- items: [
702
- { price: 10, active: true },
703
- { price: 20, active: false },
704
- { price: 30, active: true }
705
- ]
706
- }); // 40
421
+ // Condition
422
+ $cond($("age"), "gte", 18) // { left: ..., op: "gte", right: 18 }
707
423
  ```
708
424
 
709
- ### Aliases
710
-
711
- | Builder | Alias |
712
- |---------|-------|
713
- | `$` | `ref` |
714
- | `$fn` | `fn` |
715
- | `$pipe` | `pipe` |
716
- | `$cond` | `cond` |
717
-
718
425
  ## TypeScript
719
426
 
720
427
  ```typescript
@@ -733,7 +440,7 @@ import type {
733
440
  CompileOptions,
734
441
  AccessorFn,
735
442
  TransformFn,
736
- TransformResult,
443
+ Transforms,
737
444
  } from '@statedelta-libs/expressions';
738
445
 
739
446
  // Type guards
@@ -744,79 +451,59 @@ import {
744
451
  isPipe,
745
452
  isCondition,
746
453
  isLiteral,
747
- isTransformResult,
748
454
  } from '@statedelta-libs/expressions';
749
455
  ```
750
456
 
751
- ## Migração de v0.1.x
752
-
753
- ### Breaking Changes
754
-
755
- 1. **Builtins removidos**: Funções não são mais hardcoded. Passe via `scope`:
756
- ```typescript
757
- // Antes
758
- compile({ $fn: "add", args: [1, 2] })
759
-
760
- // Depois
761
- compile({ $fn: "add", args: [1, 2] }, { scope: { add } })
762
- ```
763
-
764
- 2. **$fn sem args retorna função**: Para chamar sem argumentos, use `args: []`:
765
- ```typescript
766
- // Retorna a função
767
- { $fn: "sum" }
457
+ ## Performance
768
458
 
769
- // Chama a função
770
- { $fn: "getTime", args: [] }
771
- ```
459
+ ### Comparação Execução
772
460
 
773
- 3. **Removidos**: `registerFunction`, `hasFunction`, `getFunctionNames`
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%** |
774
466
 
775
- 4. **Novo**: `$pipe` como sintaxe DSL para composição
467
+ **Break-even:** ~8 execuções - após isso, `compileAST()` compensa o tempo extra de compilação.
776
468
 
777
- ### v1.0 → v1.1
469
+ ## Segurança
778
470
 
779
- 1. **Conditions internas**: `@statedelta-libs/conditions` não é mais dependência. Conditions são compiladas internamente.
471
+ Ambas as abordagens são **seguras contra injeção de código**:
780
472
 
781
- 2. **Expressions nos lados**: `left`/`right` de conditions agora aceitam qualquer `Expression` (`$fn`, `$pipe`, `$if`, etc), não apenas `Ref | literal`.
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 |
782
477
 
783
- 3. **Novo builder**: `$cond(left, op, right?)` para construir conditions com expressions:
784
- ```typescript
785
- $cond($fn("add", [$("a"), $("b")]), "gt", 100)
786
- ```
478
+ Funções são acessíveis se existirem no `scope` fornecido pelo desenvolvedor.
787
479
 
788
- 4. **Novos tipos**: `ConditionOp`, `Condition`, `ConditionGroup`, `ConditionExpr` exportados diretamente do expressions.
480
+ ## Migração
789
481
 
790
482
  ### v1.1 → v1.2
791
483
 
792
- 1. **Custom Transforms**: Nova option `transforms` em `CompileOptions` e `CompileASTOptions`. Permite estender o walker com tipos de expressão customizados (`$query`, `$mapper`, etc.):
484
+ 1. **DSL Puro**: `FnExpr.args` agora é `Expression[]` (removido suporte a callbacks inline). Funções devem ser passadas via scope:
793
485
  ```typescript
794
- const transforms = {
795
- $query: (node) => ({ $fn: "__query", args: [node.$query, node.params ?? {}] }),
796
- };
797
- compile(expr, { scope, transforms });
798
- compileAST(expr, { scope, transforms });
799
- ```
486
+ // Antes (callbacks inline - não funciona mais)
487
+ { $fn: "filter", args: [(item) => item.active] }
800
488
 
801
- 2. **extractDeps com transforms**: `extractDeps` agora aceita `transforms` como segundo argumento para capturar deps dentro de expressões customizadas:
802
- ```typescript
803
- extractDeps(expr, transforms);
489
+ // Depois (referência via scope)
490
+ { $fn: "filter", args: [{ $fn: "isActive" }] }
491
+ // Com scope: { isActive: (item) => item.active, filter: ... }
804
492
  ```
805
493
 
806
- 3. **Novos tipos**: `TransformFn` e `TransformResult` exportados diretamente do expressions.
494
+ 2. **Removidos**: `CallbackFn`, `FnArg` types
807
495
 
808
- 4. **TransformResult**: Transforms podem retornar `TransformResult` com `args: unknown[]` para tipos domain-specific:
496
+ 3. **normalize()**: Custom transforms agora são externos via `normalize()`, não mais dentro de `CompileOptions`:
809
497
  ```typescript
810
- // TransformResult.args aceita unknown[] — sem casts necessários
811
- const transforms: Record<string, TransformFn> = {
812
- $test: (node) => ({
813
- $fn: "__test",
814
- args: [node.$test] // TestSpec, QueryParams, etc.
815
- }),
816
- };
498
+ // Antes
499
+ compile(expr, { scope, transforms });
500
+
501
+ // Depois
502
+ const pure = normalize(expr, transforms);
503
+ compile(pure, { scope });
817
504
  ```
818
505
 
819
- 5. **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>`)
820
507
 
821
508
  ## Licença
822
509