@statedelta-libs/expressions 2.2.0 → 3.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
@@ -5,779 +5,158 @@
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
- ## Características
9
-
10
- - **DSL Puro** - 100% JSON-serializável, sem callbacks inline
11
- - **Compilação** - Compila uma vez, executa milhões de vezes
12
- - **Alta performance** - ~25-30M ops/s após compilação
13
- - **Scope externo** - Funções puras vêm via scope, não hardcoded
14
- - **Context externo** - HOCs de continuidade via context (`tryCatch`, `transaction`, etc.)
15
- - **Accessor customizado** - Suporte a `ctx.get(path)` para objetos especiais
16
- - **$pipe** - Composição com valor inicial (sintaxe DSL)
17
- - **$cb** - Callback expressions para HOCs de continuidade
18
- - **Path syntax** - Suporte a wildcards (`items[*].price`)
19
- - **Dependency extraction** - Para dirty tracking/reatividade
20
- - **Conditions nativo** - Condicionais integradas com expressions nos lados
21
- - **normalize()** - Helper externo para custom transforms (`$query`, `$mapper`, etc.)
22
- - **resolveBoundaries()** - Resolve boundaries customizados (`$simulate`, `$context`, etc.) com compilação independente
23
- - **Type-safe** - TypeScript nativo com inferência
8
+ ## O que é
9
+
10
+ Um compilador que transforma expressões declarativas em JSON puro em funções JavaScript de alta performance. A entrada é sempre JSON serializável. A saída é sempre uma `(data) => result`.
11
+
12
+ O compilador não conhece nenhuma função todas vêm via `scope` fornecido pelo consumer. O mesmo motor serve para matemática, queries de game engine, ETL, NoCode, ou qualquer domínio.
24
13
 
25
14
  ## Instalação
26
15
 
27
16
  ```bash
28
- npm install @statedelta-libs/expressions
29
- # ou
30
17
  pnpm add @statedelta-libs/expressions
31
18
  ```
32
19
 
33
20
  ## Quick Start
34
21
 
35
22
  ```typescript
36
- import { compile } from '@statedelta-libs/expressions';
37
-
38
- // Define o scope com funções disponíveis
39
- const scope = {
40
- filter: (pred) => (arr) => arr?.filter(pred),
41
- map: (fn) => (arr) => arr?.map(fn),
42
- sum: (arr) => arr?.reduce((a, b) => a + b, 0),
43
- isActive: (item) => item.active,
44
- getPrice: (item) => item.price,
45
- };
23
+ import { ExpressionCompiler } from '@statedelta-libs/expressions';
24
+
25
+ const compiler = new ExpressionCompiler({
26
+ scope: {
27
+ add: (a, b) => a + b,
28
+ multiply: (a, b) => a * b,
29
+ filter: (pred) => (arr) => arr.filter(pred),
30
+ sum: (arr) => arr.reduce((a, b) => a + b, 0),
31
+ },
32
+ });
46
33
 
47
- // Compilar expressão com $pipe (DSL puro)
48
- const expr = compile({
34
+ // Compila uma vez
35
+ const compiled = compiler.compile({
49
36
  $pipe: [
50
37
  { $: "items" },
51
- { $fn: "filter", args: [{ $fn: "isActive" }] },
52
- { $fn: "map", args: [{ $fn: "getPrice" }] },
38
+ { $fn: "filter", args: [{ $arrow: { $: "item.active" }, args: ["item"] }] },
53
39
  { $fn: "sum" }
54
40
  ]
55
- }, { scope });
56
-
57
- // Executar (muito rápido!)
58
- const data = {
59
- items: [
60
- { name: "A", price: 10, active: true },
61
- { name: "B", price: 20, active: false },
62
- { name: "C", price: 30, active: true }
63
- ]
64
- };
65
-
66
- expr.fn(data); // 40
67
- expr.deps; // ["items"]
68
- ```
69
-
70
- ## API
71
-
72
- ### compile()
73
-
74
- Compila expressão JSON DSL para função otimizada usando closures.
75
-
76
- ```typescript
77
- const { fn, deps, hash } = compile(expression, { scope });
78
-
79
- fn(data); // Executa
80
- deps; // Paths que a expressão depende
81
- hash; // Hash único (para cache)
82
- ```
83
-
84
- ### compileAST()
85
-
86
- Compila usando AST + `new Function()`. **Mais rápido na execução**, ideal para expressões executadas muitas vezes.
87
-
88
- ```typescript
89
- import { compileAST } from '@statedelta-libs/expressions';
90
-
91
- const { fn, deps, hash } = compileAST(expression, { scope });
92
-
93
- fn(data); // Executa (mais rápido que compile())
94
- ```
95
-
96
- **Quando usar cada um:**
97
-
98
- | Cenário | Recomendação | Por quê |
99
- |---------|--------------|---------|
100
- | Execução única | `compile()` | Compilação mais rápida |
101
- | Poucas execuções (<8x) | `compile()` | Overhead de AST não compensa |
102
- | Muitas execuções (>8x) | `compileAST()` | Execução ~25-170% mais rápida |
103
- | Hot path crítico | `compileAST()` | V8 JIT otimiza melhor |
104
- | Expressões complexas | `compileAST()` | Ganho maior em nested calls |
105
-
106
- #### useAccessor (compileAST)
107
-
108
- Para contextos inteligentes (ex: `TickContext`), use `useAccessor: true` para gerar `accessor(path, data)` ao invés de acesso direto:
109
-
110
- ```typescript
111
- const ctx = new TickContext();
112
-
113
- const { fn } = compileAST(
114
- { $fn: "add", args: [{ $: "hp" }, { $: "mp" }] },
115
- {
116
- scope: {
117
- add,
118
- accessor: (path) => ctx.get(path) // closure que captura ctx
119
- },
120
- useAccessor: true
121
- }
122
- );
123
-
124
- fn(); // usa ctx.get("hp") + ctx.get("mp")
125
- ```
126
-
127
- #### lexicalPrefix (compileAST)
128
-
129
- Para máxima performance em paths específicos, use `lexicalPrefix` para acesso direto via destructuring:
130
-
131
- ```typescript
132
- const ctx = new TickContext();
133
-
134
- const { fn } = compileAST(
135
- { $fn: "add", args: [{ $: "hp" }, { $: "params.damage" }] },
136
- {
137
- scope: {
138
- add,
139
- accessor: (path) => ctx.get(path)
140
- },
141
- useAccessor: true,
142
- lexicalPrefix: "params" // params.* vai direto, resto via accessor
143
- }
144
- );
145
-
146
- fn({ params: { damage: 25 } }); // ctx.get("hp") + params.damage
147
- ```
148
-
149
- ### evaluate() / evaluateAST()
150
-
151
- Compila e executa em um passo.
152
-
153
- ```typescript
154
- const result = evaluate(
155
- { $fn: "add", args: [{ $: "a" }, { $: "b" }] },
156
- { a: 1, b: 2 },
157
- { scope: { add: (a, b) => a + b } }
158
- );
159
- // 3
160
-
161
- // Versão AST
162
- const result = evaluateAST(expression, data, { scope });
163
- ```
164
-
165
- ### extractDeps()
166
-
167
- Extrai dependências sem compilar.
168
-
169
- ```typescript
170
- const deps = extractDeps({
171
- $if: "$isVip",
172
- then: { $: "price.vip" },
173
- else: { $: "price.regular" }
174
41
  });
175
- // ["$isVip", "price.vip", "price.regular"]
176
- ```
177
-
178
- ### normalize()
179
-
180
- Normaliza expressões customizadas para DSL puro **antes** da compilação.
181
-
182
- ```typescript
183
- import { normalize, type Transforms } from '@statedelta-libs/expressions';
184
-
185
- const transforms: Transforms = {
186
- $query: (node) => ({
187
- $fn: "__query",
188
- args: [node.$query as string, (node.params ?? {}) as Expression],
189
- }),
190
- };
191
42
 
192
- // Converte expressão customizada para DSL puro
193
- const pure = normalize(
194
- { $query: "isAttacked", params: { row: { $: "kingRow" } } },
195
- transforms
196
- );
197
- // → { $fn: "__query", args: ["isAttacked", { row: { $: "kingRow" } }] }
198
-
199
- // Agora pode compilar normalmente
200
- const { fn } = compile(pure, { scope });
43
+ // Executa milhões de vezes
44
+ compiled.fn({ items: [{ active: true, price: 10 }, { active: false, price: 20 }] });
45
+ compiled.deps; // ["items"]
201
46
  ```
202
47
 
203
- ## Tipos de Expressão
204
-
205
- ### Literal
206
-
207
- ```typescript
208
- 42
209
- "hello"
210
- true
211
- [1, 2, 3]
212
- { a: 1 }
213
- ```
48
+ ## Os 5 Primitivos
214
49
 
215
- ### Reference ($)
50
+ | Primitivo | O que faz | Exemplo DSL | JS equivalente |
51
+ |-----------|-----------|-------------|----------------|
52
+ | `$` | Referência a path | `{ $: "user.name" }` | `data.user.name` |
53
+ | `$fn` | Invocação/referência de função | `{ $fn: "add", args: [...] }` | `add(a, b)` |
54
+ | `$if` | Condicional | `{ $if: cond, then: a, else: b }` | `cond ? a : b` |
55
+ | `$pipe` | Composição left-to-right | `{ $pipe: [x, f, g] }` | `g(f(x))` |
56
+ | `$arrow` | Closure deferida | `{ $arrow: body, args: ["x"] }` | `(x) => body` |
216
57
 
217
- ```typescript
218
- { $: "user.name" } // Acessa user.name
219
- { $: "items[0].price" } // Acessa índice
220
- { $: "items[*].price" } // Wildcard → [10, 20, 30]
221
- ```
58
+ Tudo 100% JSON-serializável. Funções nunca aparecem no DSL — são referenciadas por nome e resolvidas contra o scope em runtime.
222
59
 
223
- ### Conditional ($if)
60
+ ## Dois Modos de Compilação
224
61
 
225
62
  ```typescript
226
- {
227
- $if: { left: { $: "age" }, op: "gte", right: 18 },
228
- then: "adult",
229
- else: "minor"
230
- }
231
-
232
- // Shorthand
233
- { $if: "$isVip", then: 0.2, else: 0 }
234
- { $if: "!$isBlocked", then: "allowed" }
63
+ compiler.compile(expr); // closures — compilação rápida, ~9-20M ops/s
64
+ compiler.jit(expr); // JIT compilação lenta, ~25-27M ops/s de execução
235
65
  ```
236
66
 
237
- ### Pipe ($pipe)
238
-
239
- Composição com valor inicial - o valor passa por cada função em sequência.
67
+ **Closures** compõe funções JavaScript aninhadas. Ideal para expressões executadas poucas vezes ou ambientes com CSP restritivo.
240
68
 
241
- ```typescript
242
- {
243
- $pipe: [
244
- { $: "items" }, // Valor inicial
245
- { $fn: "filter", args: [{ $fn: "isActive" }] }, // Curried filter
246
- { $fn: "map", args: [{ $fn: "getPrice" }] }, // Curried map
247
- { $fn: "sum" } // Referência à função
248
- ]
249
- }
250
- ```
69
+ **JIT** gera código JavaScript via AST e `new Function()`. Ideal para hot paths executados muitas vezes. Break-even em ~8 execuções.
251
70
 
252
- ### Function Call ($fn)
71
+ ## Extensibilidade
253
72
 
254
- Chama função do scope com argumentos compilados.
73
+ ### Scope funções disponíveis
255
74
 
256
75
  ```typescript
257
- // Com args: chama a função
258
- { $fn: "add", args: [{ $: "a" }, { $: "b" }] }
259
- // → scope.add(data.a, data.b)
260
-
261
- // Sem args: retorna referência à função (útil em $pipe)
262
- { $fn: "sum" }
263
- // scope.sum
264
-
265
- // Com args vazio: chama função sem argumentos
266
- { $fn: "getTimestamp", args: [] }
267
- // → scope.getTimestamp()
268
-
269
- // Passando função como argumento (referência via $fn)
270
- { $fn: "filter", args: [{ $fn: "isActive" }] }
271
- // → scope.filter(scope.isActive)
272
- ```
273
-
274
- ### Condition
275
-
276
- Condicionais compiladas internamente. Ambos os lados aceitam qualquer expressão:
277
-
278
- ```typescript
279
- // Ref vs literal (básico)
280
- { left: { $: "user.age" }, op: "gte", right: 18 }
281
-
282
- // Ref vs Ref
283
- { left: { $: "price" }, op: "lte", right: { $: "budget" } }
284
-
285
- // $fn nos lados
286
- { left: { $fn: "add", args: [{ $: "a" }, { $: "b" }] }, op: "gt", right: 100 }
287
-
288
- // $pipe no lado
289
- { left: { $pipe: [{ $: "name" }, { $fn: "upper" }] }, op: "eq", right: "ADMIN" }
290
-
291
- // Condition group
292
- {
293
- logic: "AND",
294
- conditions: [
295
- { left: { $: "active" }, op: "eq", right: true },
296
- { left: { $fn: "len", args: [{ $: "items" }] }, op: "gte", right: 3 }
297
- ]
298
- }
299
- ```
300
-
301
- **Operadores:** `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`, `contains`, `notContains`, `exists`, `notExists`, `matches`, `notMatches`, `startsWith`, `endsWith`
302
-
303
- ### Callback ($cb)
304
-
305
- HOC de continuidade - executa body dentro de um context function.
306
-
307
- ```typescript
308
- // Básico - tryCatch
309
- { $cb: "tryCatch", body: { $fn: "query" } }
310
-
311
- // Com parâmetros
312
- { $cb: "transaction", body: { $fn: "save" }, params: { isolation: "serializable" } }
313
-
314
- // Aninhado
315
- {
316
- $cb: "tryCatch",
317
- body: {
318
- $cb: "transaction",
319
- body: { $fn: "save" }
320
- }
321
- }
322
- ```
323
-
324
- ## Context
325
-
326
- O context define HOCs disponíveis para `$cb`. Diferente de scope (funções puras), context functions controlam a execução do body:
327
-
328
- ```typescript
329
- import type { Context, ContextFn } from '@statedelta-libs/expressions';
330
-
331
- const tryCatch: ContextFn = (cb, data, get, params) => {
332
- try {
333
- return cb(data, get);
334
- } catch {
335
- return params?.fallback ?? null;
336
- }
337
- };
338
-
339
- const transaction: ContextFn = (cb, data, get, params) => {
340
- const tx = db.beginTransaction(params);
341
- try {
342
- const result = cb({ ...data, tx }, get);
343
- tx.commit();
344
- return result;
345
- } catch (e) {
346
- tx.rollback();
347
- throw e;
348
- }
349
- };
350
-
351
- // Context function pode ignorar o cb e retornar outro valor
352
- const cached: ContextFn = (cb, data, get, params) => {
353
- if (cache.has(params?.key)) {
354
- return cache.get(params.key); // Não executa cb
355
- }
356
- return cb(data, get);
357
- };
358
-
359
- const context: Context = { tryCatch, transaction, cached };
360
-
361
- compile(expression, { scope, context });
362
- ```
363
-
364
- **Parâmetros do ContextFn:**
365
-
366
- | Parâmetro | Tipo | Descrição |
367
- |-----------|------|-----------|
368
- | `cb` | `(data, get) => R` | Body compilado como callback |
369
- | `data` | `T` | Dados do runtime |
370
- | `get` | `(path) => unknown` | Resolver de paths |
371
- | `params` | `unknown` | Parâmetros do `$cb.params` |
372
-
373
- ## Scope
374
-
375
- O scope define quais funções estão disponíveis para `$fn`:
376
-
377
- ```typescript
378
- import * as R from '@statedelta-libs/operators';
379
-
380
- // Todas as funções do operators
381
- const scope = R;
382
-
383
- // Ou seleção específica
384
- const scope = {
385
- add: R.add,
386
- filter: R.filter,
387
- map: R.map,
388
- sum: R.sum,
389
- // Funções custom (predicados, mappers, etc.)
390
- double: (n) => n * 2,
391
- isActive: (item) => item.active,
392
- getPrice: (item) => item.price,
393
- };
394
-
395
- const { fn } = compile(expression, { scope });
396
- ```
397
-
398
- ## Custom Transforms (normalize)
399
-
400
- Para tipos de expressão customizados (`$query`, `$mapper`, etc.), use `normalize()` para convertê-los em DSL puro **antes** da compilação:
401
-
402
- ```typescript
403
- import { normalize, compile, type Transforms, type Expression } from '@statedelta-libs/expressions';
404
-
405
- // Define transforms
406
- const transforms: Transforms = {
407
- $query: (node) => ({
408
- $fn: "__query",
409
- args: [node.$query as string, (node.params ?? {}) as Expression],
410
- }),
411
- $mapper: (node) => ({
412
- $fn: "__mapper",
413
- args: [node.$mapper as string],
414
- }),
415
- };
416
-
417
- // Define scope com as funções de runtime
418
- const scope = {
419
- __query: (name, params) => queryEngine.run(name, params),
420
- __mapper: (name) => mapperRegistry.get(name),
421
- };
422
-
423
- // 1. Normalize (converte para DSL puro)
424
- const pure = normalize(
425
- { $query: "isAttacked", params: { row: { $: "kingRow" } } },
426
- transforms
427
- );
428
-
429
- // 2. Compile (DSL puro)
430
- const { fn } = compile(pure, { scope });
431
-
432
- // 3. Execute
433
- fn({ kingRow: 0 });
434
- ```
435
-
436
- ### Aninhamento
437
-
438
- O `normalize` percorre toda a árvore, então transforms funcionam em qualquer posição:
439
-
440
- ```typescript
441
- normalize(
442
- {
443
- $if: {
444
- left: { $query: "isAttacked", params: { row: 0 } },
445
- op: "eq",
446
- right: true
76
+ const compiler = new ExpressionCompiler({
77
+ scope: {
78
+ add: (a, b) => a + b,
79
+ filter: (pred) => (arr) => arr.filter(pred),
80
+ tryCatch: (arrow, params) => {
81
+ try { return arrow(); }
82
+ catch { return params?.fallback ?? null; }
447
83
  },
448
- then: "check",
449
- else: "safe"
450
84
  },
451
- transforms
452
- );
85
+ });
453
86
  ```
454
87
 
455
- ### Proteção contra loops
88
+ ### Normalize DSL customizado
456
89
 
457
- Se um transform retornar objeto com a mesma chave, erro é lançado:
90
+ Transforma sugar syntax em DSL puro **antes** da compilação. Para quando o resultado é expression DSL válido.
458
91
 
459
92
  ```typescript
460
- const badTransforms = {
461
- $loop: (node) => ({ $loop: node.$loop }), // mesma chave!
93
+ const transforms = {
94
+ $double: (node) => ({ $fn: "multiply", args: [node.$double, 2] }),
462
95
  };
463
96
 
464
- normalize({ $loop: "test" }, badTransforms);
465
- // Error: Transform "$loop" returned object with same key — infinite loop
97
+ const pure = compiler.normalize({ $double: { $: "value" } }, transforms);
98
+ compiler.compile(pure);
466
99
  ```
467
100
 
468
- ## Boundaries (resolveBoundaries)
469
-
470
- Para DSL customizados que precisam de **compilação independente** dos slots internos, use `resolveBoundaries()`. Diferente de `normalize()` (que apenas transforma), boundaries param o fluxo de compilação e delegam para um resolver externo.
471
-
472
- ### Conceito
101
+ ### Boundaries — compiladores externos
473
102
 
474
- Um **boundary** é um "corpo estranho" no DSL que:
475
- 1. **Para** o fluxo normal de compilação
476
- 2. **Delega** para um resolver customizado
477
- 3. O resolver **compila slots internos** independentemente
478
- 4. **Substitui** por uma referência no scope
479
-
480
- ```
481
- DSL "rico" DSL puro
482
- { $simulate: {...} } → { $fn: "__simulate_0", args: [] }
483
- + scope["__simulate_0"] = fn compilada
484
- ```
485
-
486
- ### Uso básico
103
+ Intercepta nós **durante** a compilação e terceiriza para outro algoritmo. Para quando o nó precisa de um compilador completamente diferente.
487
104
 
488
105
  ```typescript
489
- import { resolveBoundaries, compile, type BoundaryResolvers } from '@statedelta-libs/expressions';
490
-
491
- const resolvers: BoundaryResolvers = {
492
- $context: (node, { compile: compileFn, genId, scope }) => {
493
- const id = `__context_${genId()}`;
494
- const body = (node.$context as { body: Expression }).body;
495
-
496
- // Compila o body internamente
497
- const bodyFn = compileFn(body, { scope }).fn;
498
-
499
- // Retorna função com lifecycle begin/end
500
- const contextFn = () => (data: unknown) => {
501
- console.log("begin");
502
- try {
503
- return bodyFn(data);
504
- } finally {
505
- console.log("end");
506
- }
507
- };
508
-
509
- return {
510
- expr: { $fn: id, args: [] },
511
- scopeEntry: [id, contextFn],
512
- };
513
- },
514
- };
106
+ import type { BoundaryDef } from '@statedelta-libs/expressions';
515
107
 
516
- // 1. Resolve boundaries (extrai e compila internamente)
517
- const { expr, scope } = resolveBoundaries(
518
- { $context: { body: { $fn: "add", args: [{ $: "a" }, { $: "b" }] } } },
519
- { resolvers, scope: { add: (a, b) => a + b } }
520
- );
521
-
522
- // 2. Compila DSL puro resultante
523
- const { fn } = compile(expr, { scope });
524
-
525
- // 3. Executa
526
- const contextFn = fn({}); // Retorna a função com lifecycle
527
- const result = contextFn({ a: 1, b: 2 }); // "begin" → 3 → "end"
528
- ```
529
-
530
- ### Caso de uso: $simulate com múltiplos slots
531
-
532
- ```typescript
533
- interface SimulateNode {
534
- $simulate: {
535
- effects: Expression[];
536
- query: Expression;
537
- };
538
- }
539
-
540
- const resolvers: BoundaryResolvers = {
541
- $simulate: (node, { compile: compileFn, genId, scope }) => {
542
- const id = `__simulate_${genId()}`;
543
- const sim = (node as unknown as SimulateNode).$simulate;
544
-
545
- // Compila cada slot independentemente
546
- const effectFns = sim.effects.map((e) => compileFn(e, { scope }).fn);
547
- const queryFn = compileFn(sim.query, { scope }).fn;
548
-
549
- // Cria função que orquestra a execução
550
- const simulateFn = () => (data: unknown) => {
551
- const effects = effectFns.map((fn) => fn(data));
552
- const query = queryFn(data);
553
- return { effects, query };
554
- };
555
-
556
- return {
557
- expr: { $fn: id, args: [] },
558
- scopeEntry: [id, simulateFn],
559
- };
560
- },
108
+ // $raw passthrough, nada é compilado
109
+ const rawBoundary: BoundaryDef = {
110
+ check: (node) => "$raw" in node,
111
+ handle: (node) => () => node.$raw,
561
112
  };
562
113
 
563
- const { expr, scope } = resolveBoundaries(
564
- {
565
- $simulate: {
566
- effects: [
567
- { $fn: "increment", args: [{ $: "hp" }] },
568
- { $fn: "decrement", args: [{ $: "mp" }] },
569
- ],
570
- query: { $fn: "isAlive", args: [{ $: "hp" }] },
571
- },
114
+ // $rules DSL estrangeiro com compilador próprio
115
+ const rulesBoundary: BoundaryDef = {
116
+ check: (node) => "$rules" in node,
117
+ handle: (node) => {
118
+ const compiled = ruleEngine.compile(node.$rules);
119
+ return (data) => compiled.evaluate(data);
572
120
  },
573
- { resolvers, scope: myScope }
574
- );
575
- ```
576
-
577
- ### Contexto do resolver
578
-
579
- O resolver recebe um contexto com:
580
-
581
- | Propriedade | Tipo | Descrição |
582
- |-------------|------|-----------|
583
- | `compile` | `typeof compile` | Mesmo compilador do fluxo principal |
584
- | `genId` | `() => string` | Gerador de IDs únicos |
585
- | `scope` | `Scope` | Scope atual (read-only) |
586
- | `options` | `CompileOptions` | Opções de compilação |
587
-
588
- ### ID Generator customizado
589
-
590
- Por padrão, IDs são gerados como `"0"`, `"1"`, `"2"`... Para IDs únicos globais, passe um `genId` customizado:
121
+ };
591
122
 
592
- ```typescript
593
- import { nanoid } from 'nanoid';
123
+ const compiler = new ExpressionCompiler({
124
+ scope,
125
+ boundaries: [rawBoundary, rulesBoundary],
126
+ });
594
127
 
595
- const { expr, scope } = resolveBoundaries(expr, {
596
- resolvers,
597
- scope: baseScope,
598
- genId: () => nanoid(), // IDs únicos globais
128
+ // $rules é interceptado pelo boundary, compilado pelo ruleEngine
129
+ compiler.compile({
130
+ $fn: "add",
131
+ args: [{ $: "base" }, { $rules: { when: "vip", then: 20 } }]
599
132
  });
600
133
  ```
601
134
 
602
- ### Diferença entre normalize e resolveBoundaries
135
+ O handler é uma closure auto-suficiente — captura o que precisa (outros compiladores, databases, etc.) por fora. Zero overhead quando não há boundaries registrados.
603
136
 
604
- | | `normalize()` | `resolveBoundaries()` |
137
+ | | `normalize()` | `BoundaryDef` |
605
138
  |---|---|---|
606
- | **Propósito** | Transformar sintaxe | Compilação independente |
607
- | **Retorno** | `Expression` | `{ expr, scope }` |
608
- | **Compila slots** | Não | Sim (via `ctx.compile`) |
609
- | **Acumula scope** | Não | Sim |
610
- | **Uso típico** | `$query` → `$fn` | `$simulate`, `$context` |
139
+ | **Quando roda** | Antes da compilação | Durante a compilação (no walk) |
140
+ | **Retorno** | `Expression` (DSL puro) | `CompiledFn` (função pronta) |
141
+ | **Uso típico** | Sugar syntax | DSL estrangeiro, `$raw`, rule engines |
611
142
 
612
- Use `normalize()` para sugar syntax simples. Use `resolveBoundaries()` quando precisar:
613
- - Compilar múltiplos slots independentemente
614
- - Controlar ordem/momento de execução
615
- - Adicionar lógica de lifecycle (begin/end, try/finally)
616
- - DSL completamente diferente internamente
617
-
618
- ## Builders
619
-
620
- Funções declarativas para construir expressões:
143
+ ### Accessor objetos inteligentes
621
144
 
622
145
  ```typescript
623
- import { builders } from '@statedelta-libs/expressions';
624
-
625
- const { $, $fn, $if, $pipe, $cond, $cb } = builders;
626
-
627
- // Path reference
628
- $("player.hp") // { $: "player.hp" }
629
-
630
- // Function call
631
- $fn("add", [$("a"), $("b")]) // { $fn: "add", args: [...] }
632
- $fn("sum") // { $fn: "sum" }
633
-
634
- // Conditional
635
- $if($("isVip"), 0.2, 0) // { $if: ..., then: 0.2, else: 0 }
636
-
637
- // Pipe
638
- $pipe(
639
- $("items"),
640
- $fn("filter", [$fn("isActive")]),
641
- $fn("sum")
642
- )
643
-
644
- // Condition
645
- $cond($("age"), "gte", 18) // { left: ..., op: "gte", right: 18 }
646
-
647
- // Callback (context)
648
- $cb("tryCatch", $fn("query")) // { $cb: "tryCatch", body: {...} }
649
- $cb("transaction", $fn("save"), { isolation: "serializable" })
650
- ```
651
-
652
- ## TypeScript
653
-
654
- ```typescript
655
- import type {
656
- Expression,
657
- CompiledExpression,
658
- RefExpr,
659
- ConditionalExpr,
660
- FnExpr,
661
- PipeExpr,
662
- CbExpr,
663
- Condition,
664
- ConditionGroup,
665
- ConditionExpr,
666
- ConditionOp,
667
- Scope,
668
- Context,
669
- ContextFn,
670
- PathGetterFn,
671
- CompileOptions,
672
- AccessorFn,
673
- TransformFn,
674
- Transforms,
675
- // Boundaries
676
- BoundaryResolver,
677
- BoundaryResolvers,
678
- ResolverContext,
679
- ResolverResult,
680
- ResolveBoundariesOptions,
681
- ResolveBoundariesResult,
682
- IdGenerator,
683
- } from '@statedelta-libs/expressions';
684
-
685
- // Type guards
686
- import {
687
- isRef,
688
- isConditional,
689
- isFn,
690
- isPipe,
691
- isCb,
692
- isCondition,
693
- isLiteral,
694
- } from '@statedelta-libs/expressions';
146
+ const compiler = new ExpressionCompiler({
147
+ scope,
148
+ accessor: (path, ctx) => ctx.get(path), // ex: TickContext, reactive store
149
+ });
695
150
  ```
696
151
 
697
- ## Performance
698
-
699
- ### Comparação Execução
700
-
701
- | Cenário | Closures | AST | Ganho AST |
702
- |---------|----------|-----|-----------|
703
- | fn nested | 9M ops/s | 25M ops/s | **+169%** |
704
- | ref 4 níveis | 16M ops/s | 27M ops/s | **+69%** |
705
- | conditional nested | 20M ops/s | 25M ops/s | **+25%** |
706
-
707
- **Break-even:** ~8 execuções - após isso, `compileAST()` compensa o tempo extra de compilação.
708
-
709
- ## Segurança
710
-
711
- Ambas as abordagens são **seguras contra injeção de código**:
712
-
713
- | Método | Proteção |
714
- |--------|----------|
715
- | `compile()` | Não gera código string - apenas compõe funções |
716
- | `compileAST()` | Usa destructuring que valida identificadores automaticamente |
717
-
718
- Funções só são acessíveis se existirem no `scope` fornecido pelo desenvolvedor.
719
-
720
- ## Migração
721
-
722
- ### v1.1 → v1.2
723
-
724
- 1. **DSL Puro**: `FnExpr.args` agora é `Expression[]` (removido suporte a callbacks inline). Funções devem ser passadas via scope:
725
- ```typescript
726
- // Antes (callbacks inline - não funciona mais)
727
- { $fn: "filter", args: [(item) => item.active] }
728
-
729
- // Depois (referência via scope)
730
- { $fn: "filter", args: [{ $fn: "isActive" }] }
731
- // Com scope: { isActive: (item) => item.active, filter: ... }
732
- ```
733
-
734
- 2. **Removidos**: `CallbackFn`, `FnArg` types
735
-
736
- 3. **normalize()**: Custom transforms agora são externos via `normalize()`, não mais dentro de `CompileOptions`:
737
- ```typescript
738
- // Antes
739
- compile(expr, { scope, transforms });
740
-
741
- // Depois
742
- const pure = normalize(expr, transforms);
743
- compile(pure, { scope });
744
- ```
745
-
746
- 4. **Novos tipos**: `Transforms` (alias para `Record<string, TransformFn>`)
747
-
748
- ### v1.2 → v2.0
749
-
750
- 1. **Novo tipo `$cb`**: HOC de continuidade para wrapping contextual:
751
- ```typescript
752
- // tryCatch, transaction, cached, etc.
753
- { $cb: "tryCatch", body: { $fn: "query" }, params: { fallback: [] } }
754
- ```
755
-
756
- 2. **Novo `context` em CompileOptions**: Separado de `scope` para HOCs:
757
- ```typescript
758
- compile(expr, { scope, context });
759
- compileAST(expr, { scope, context });
760
- ```
761
-
762
- 3. **Novos tipos**: `CbExpr`, `Context`, `ContextFn`, `PathGetterFn`
763
-
764
- 4. **Novo type guard**: `isCb()`
765
-
766
- 5. **Novo builder**: `$cb(name, body, params?)`
767
-
768
- 6. **Novo extractor**: `extractContextFns()`
769
-
770
- 7. **Novo `resolveBoundaries()`**: Para DSL customizados com compilação independente de slots:
771
- ```typescript
772
- const { expr, scope } = resolveBoundaries(richExpr, {
773
- resolvers: { $simulate: (node, ctx) => ... },
774
- scope: baseScope,
775
- genId: () => nanoid(), // opcional
776
- });
777
- compile(expr, { scope });
778
- ```
152
+ ## Documentação
779
153
 
780
- 8. **Novos tipos**: `BoundaryResolver`, `BoundaryResolvers`, `ResolverContext`, `ResolverResult`, `ResolveBoundariesOptions`, `ResolveBoundariesResult`, `IdGenerator`
154
+ | Doc | Conteúdo |
155
+ |-----|----------|
156
+ | [docs/EXPRESSIONS-API.md](docs/EXPRESSIONS-API.md) | Referência completa da API pública |
157
+ | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Visão geral da infraestrutura |
158
+ | [docs/COMPILE.md](docs/COMPILE.md) | Internals do pipeline closures |
159
+ | [docs/JIT.md](docs/JIT.md) | Internals do pipeline JIT |
781
160
 
782
161
  ## Licença
783
162