npm package: @statedelta-actions/actions
Description: Directive execution engine with JIT compilation and BailHook interception
Author: Anderson D. Rosa
Latest version: 0.3.0

# @statedelta-actions/actions

Motor de execução de diretivas com validação em register-time e compilação JIT.

Defina ações como objetos JS declarativos. Registre. O engine valida estrutura, compila executores otimizados. Em runtime, invoke é O(1) lookup + execução — zero validação, zero análise.

## Filosofia

**Engine = V8. Runtime puro.**

O engine é um executor. Não analisa dependências, não computa grafos, não valida contratos. Registro valida estrutura e compila. Invocação executa. Análise estática (grafo, capabilities, ciclos, composition control) é responsabilidade do `@statedelta-actions/analyzer` — uma camada externa opcional.

Ações são **declarativas**. Uma action é um ID mais uma lista ordenada de diretivas — objetos JS declarativos despachados por um campo de tipo (`type` por padrão). O engine não sabe o que "state", "emit" ou "action" significam. Handlers dão significado às diretivas. O engine orquestra a execução.

**Handlers têm fases.** Um handler não é só um executor. Ele pode validar estrutura da diretiva em register-time, contribuir código JIT, e executar em runtime. Cada fase é opcional — uma função simples funciona como handler (compat V1). Uma definição completa desbloqueia todo o pipeline. A fase `analyze` existe na interface mas é consumida pelo `ActionAnalyzer`, não pelo engine.

**Sem sugar no runtime.** Diretivas devem ser passadas em **forma canônica** — toda diretiva tem campo `type`. Sugar forms (shorthands de autoria) são concern do compilador JSON DSL, que normaliza antes de entregar pro engine. O engine não interpreta nem converte sugar.

## Instalação

```bash
pnpm add @statedelta-actions/actions
```

## Início Rápido

```typescript
import { createActionEngine } from "@statedelta-actions/actions";

// 1. Defina handlers — dão significado aos tipos de diretiva
const handlers = {
  state: {
    execute: (directive, frame) => {
      const { target, value } = directive;
      frame.ctx.state[target] = value;
      return { ok: true, data: value };
    },
  },
  emit: {
    execute: (directive, frame) => {
      frame.ctx.events.push(directive.event);
      return { ok: true };
    },
  },
  action: {
    execute: (directive, frame, engine) => {
      const result = engine.invoke(directive.id, directive.params, frame);
      return { ok: result.success, data: result.data };
    },
  },
};

// 2. Crie o engine
const engine = createActionEngine({ handlers });

// 3. Registre ações
const result = engine.register([
  {
    id: "heal",
    directives: [
      { type: "state", target: "hp", value: 100 },
      { type: "emit", event: "healed" },
    ],
  },
  {
    id: "combat",
    directives: [
      { type: "action", id: "heal" },
      { type: "emit", event: "combat:done" },
    ],
  },
]);

// result.registered → ["heal", "combat"]
// result.errors     → []
// result.warnings   → []

// 4. Invoque com contexto
const ctx = { state: {}, events: [] };
const r = engine.invoke("heal", undefined, ctx);
// r.success      → true
// r.appliedCount → 2
```

## Handlers

Um handler processa diretivas de um tipo específico. Dois formatos:

### V1 — Função simples

```typescript
const handlers = {
  log: (directive, frame, engine) => {
    console.log(directive.message);
    return { ok: true };
  },
};
```

Compatível com versões anteriores. Sem análise em register-time. Emite warning `NO_ANALYZE`.

### V2 — Definição completa

```typescript
const handlers = {
  state: {
    // Register-time: valida estrutura da diretiva
    validate(directive) {
      if (!directive.target) return { valid: false, error: "missing target" };
    },

    // Runtime: executa a diretiva
    execute(directive, frame, engine) {
      frame.ctx.state[directive.target] = directive.value;
      return { ok: true, data: directive.value };
    },

    // Usado pelo ActionAnalyzer (não pelo engine):
    // analyze(directive) {
    //   return { capabilities: ["write"], dependencies: [] };
    // },
  },
};
```

| Fase | Quando | Propósito |
|------|--------|-----------|
| `validate` | Register | Validação estrutural (rejeita diretivas malformadas) |
| `execute` | Runtime | Processa a diretiva e retorna resultado |
| `analyze` | — | Consumido pelo `ActionAnalyzer` externo, não pelo engine |

## Ações

Uma ação é um ID mais diretivas:

```typescript
engine.register([{
  id: "checkout",
  directives: [
    { type: "validate", schema: "cart" },
    { type: "state", target: "status", value: "processing" },
    { type: "action", id: "checkout/charge" },
    { type: "emit", event: "checkout:complete" },
  ],
}]);
```

### Categorias de Diretiva

Três categorias. **Toda diretiva tem campo `type`** (forma canônica). O engine opera exclusivamente sobre forma canônica.

**Binding** — escrita no scope de execução:

```typescript
{ type: "const", name: "tax", value: 0.1 }
{ type: "let", name: "total", value: "$", resolve: (ctx, scope) => ({ value: scope.subtotal * 1.1 }) }
```

**Control** — saída antecipada:

```typescript
{ type: "return", value: "done" }                     // success: true, data: "done"
{ type: "throw", message: "saldo insuficiente" }      // success: false
```

**Handler** — dispatch pro handler registrado:

```typescript
{ type: "state", target: "hp", value: 100, as: "prev" }
{ type: "action", id: "heal", catch: [{ type: "emit", event: "heal:failed" }] }
```

O interpreter e JIT operam sobre formato uniforme — um único dispatch via `type` field, sem branching de categorias.

Os nomes `const`, `let`, `return`, `throw` são **tipos reservados** — o engine rejeita handlers com esses nomes.

Diretivas suportam:
- **`as`** — captura `result.data` no scope: `scope["prev"] = result.data`
- **`catch`** — em caso de falha, executa sub-diretivas com `scope.$exception`
- **`halt`** — handler retorna `{ ok: true, halt: true }` para saída antecipada
- **`resolve`** — campos dinâmicos mesclados antes da chamada: `resolve(ctx, scope) → campos mesclados`

## Registro

```typescript
const result = engine.register(actions);
```

O pipeline de registro:

1. **Validate** — valida estrutura via `handler.validate()` (rejeita diretivas malformadas). Diretivas reservadas (`const`, `let`, `return`, `throw`) são reconhecidas pelo `type` e não precisam de handler.
2. **Store** — armazena no registry
3. **Compile** — compila executor (interpret/JIT)
4. **Emit** — emite evento `register` via lifecycle events

Retorna `RegisterResult`:

```typescript
{
  registered: string[];        // IDs registrados com sucesso
  errors: RegisterError[];     // Falhas de validação
  warnings: RegisterWarning[]; // NO_ANALYZE, ANALYZE_ERROR
}
```

### Desregistrar

```typescript
engine.unregister("checkout");
// Também remove filhas: "checkout/charge", "checkout/validate", etc.
```

## Modo Batch

Batch agrupa múltiplos registros e adia emissão de eventos pro `endBatch()`:

```typescript
engine.beginBatch();
engine.register([actionA]);
engine.register([actionB]);
const result = engine.endBatch();  // Único evento "register" emitido
```

Suporta aninhamento — `endBatch()` interno é no-op. Processamento acontece na profundidade 0.

## Invocação

```typescript
// Passe ctx direto na invocação (recomendado)
const result = engine.invoke("heal", undefined, ctx);
const result = engine.invoke("heal", { amount: 50 }, ctx);

// Ou defina ctx global e invoque sem passar ctx
engine.setContext(ctx);
const result = engine.invoke("heal");

// Ou ctx temporário com escopo (múltiplas invocações)
engine.context(ctx, () => {
  engine.invoke("heal");
  engine.invoke("combat");
});

// Async (obrigatório se algum hook for async)
const result = await engine.invokeAsync("heal", undefined, ctx);
```

Três formas de fornecer ctx, em ordem de preferência:

| Forma | Quando usar |
|-------|-------------|
| `invoke(id, params, ctx)` | Invocação única, ctx por chamada — mais direto |
| `context(ctx, fn)` | Múltiplas invocações com mesmo ctx temporário |
| `setContext(ctx)` | Ctx global estável entre múltiplas invocações |

Se `ctx` for passado no `invoke`/`invokeAsync`, tem precedência sobre `setContext`/`context`. Se omitido, fallback pro ctx global (erro se nenhum definido).

Retorna `DirectiveResult`:

```typescript
{
  success: boolean;
  aborted: boolean;
  abortedBy?: string;        // "halt" | "throw" | "maxDepth"
  appliedCount: number;
  skippedCount: number;
  errors: DirectiveError[];
  data?: unknown;             // De return directive ou halt
  counters: FrameCounters;
}
```

**Nunca lança exceções.** Erros são coletados em `result.errors`. Exceções de handlers são capturadas e reportadas.

### Sub-Actions (Visibilidade)

Ações com `/` no ID são privadas:

```typescript
engine.register([
  { id: "checkout/validate", directives: [...] },  // privada
  { id: "checkout", directives: [
    { type: "action", id: "checkout/validate" },    // ok — escopo do pai
  ]},
]);

engine.invoke("checkout/validate"); // erro — não acessível da raiz
```

## Lifecycle Events

O engine emite eventos de ciclo de vida via `on()`. Retorna função de unsubscribe idempotente:

```typescript
const unsub = engine.on("register", (event) => {
  console.log("Registrados:", event.registered);
  console.log("Resultado:", event.result);
});

engine.on("unregister", (event) => {
  console.log("Removido:", event.id);
  console.log("Cascata:", event.cascaded);
});

// Cancelar subscription
unsub();
```

| Evento | Payload | Quando |
|--------|---------|--------|
| `register` | `{ actions, result, registered }` | Após `register()` ou `endBatch()` |
| `unregister` | `{ id, cascaded }` | Após `unregister()` |

Em batch mode, o evento `register` é emitido uma única vez no `endBatch()` com todas as actions do batch.

## Read-Only Accessors

Acessores de leitura para introspection por camadas externas (e.g. analyzer):

```typescript
// Map de handler definitions V2 registradas
engine.handlerDefinitions;  // ReadonlyMap<string, HandlerDefinition>

// Set de IDs de todas as actions no registry
engine.registeredIds;  // ReadonlySet<string>

// Lê uma action definition pelo ID
engine.getActionDefinition("heal");  // ActionDefinition | undefined

// Campo de tipo pra dispatch de diretivas
engine.typeField;  // string (default: "type")

// Mapa de permissões de diretivas (computado no boot, imutável)
engine.directivePermissions;  // ReadonlyMap<string, DirectivePermission>

// Slots de hooks preenchidos (nomes dos hooks registrados)
engine.directiveHookSlots;  // ReadonlySet<string>

// Slots async (nomes dos hooks que são async)
engine.asyncSlots;  // ReadonlySet<string>
```

## Modos de Compilação

```typescript
createActionEngine({ handlers, mode: "interpret" }); // Interpretador loop-based (dev)
createActionEngine({ handlers, mode: "jit" });        // Compila tudo no register (prod)
createActionEngine({ handlers, mode: "auto" });       // Interpreta primeiro, promove após N invocações (padrão)
```

| Modo | Register | Runtime | Ideal para |
|------|----------|---------|------------|
| `interpret` | Rápido | Loop interpretado | Desenvolvimento, debug |
| `jit` | Mais lento (compila) | `new Function` compilado | Produção, ações estáveis |
| `auto` | Rápido | Promove per-action após threshold | Uso geral (padrão) |

Threshold de auto-promote (padrão: 8):

```typescript
createActionEngine({ handlers, mode: "auto", autoJitThreshold: 4 });
```

Forçar compilação de todas as ações registradas:

```typescript
engine.compile();
```

Informações de compilação:

```typescript
engine.isAsync;          // true se hooks async detectados
engine.compilationMode;  // "interpret" | "jit" (modo atual, não o requestado)
```

## Hooks

Três pontos de hook em nível de diretiva:

```typescript
createActionEngine({
  handlers,
  directiveHooks: {
    beforeDirective(directive, frame) {
      // Retorne "skip" pra pular, "abort" pra abortar
      // Retorne { directive } pra substituir, { ctx } pra sobrescrever contexto
    },
    afterDirective(directive, result, frame) {
      // Retorne "abort" pra parar execução
    },
    onDirectivesComplete(result) {
      // Fire-and-forget — observa resultado final
    },
  },
});
```

Hooks podem ser sync ou async. Hooks async tornam o engine async (`engine.isAsync === true`), exigindo `invokeAsync()`.

**Custo zero quando ausente.** A compilação JIT não emite código de hook para hooks não registrados.

## Action Hooks

Hooks no nível de **action** — interceptam antes e depois de executar todas as diretivas de uma action. Diferente de `directiveHooks` que operam por diretiva individual.

```typescript
createActionEngine({
  handlers,
  actionHooks: {
    beforeAction(id, params, frame) {
      // Antes de executar qualquer diretiva da action.
      // Retorne void pra prosseguir normalmente.
      // Retorne { skip: true, data? } pra skip total (zero diretivas processadas).
    },
    afterAction(id, params, result, frame) {
      // Após execução completa. NÃO dispara se beforeAction skipou.
      // Retorne void pra manter resultado original.
      // Retorne DirectiveResult pra substituir.
    },
  },
});
```

**Use cases:** memoização, profiling, auditoria, mock/dry-run, governance.

### Memoização via beforeAction

O engine não sabe o que é memo. O consumer implementa a lógica via closure (ADR-017):

```typescript
const cache = new Map<string, { data: unknown }>();

const engine = createActionEngine({
  handlers,
  actionHooks: {
    beforeAction(id) {
      const cached = cache.get(id);
      if (cached) return { skip: true, data: cached.data };
    },
    afterAction(id, _params, result) {
      cache.set(id, { data: result.data });
    },
  },
});
```

### Comportamento

- **Frame:** O hook recebe o `childFrame` (scope filho, depth incrementado), não o frame do caller.
- **Skip e auto-promote:** Skip não incrementa `invokeCount` — não conta pro threshold de auto-promote.
- **Skip e afterAction:** Se `beforeAction` skipar, `afterAction` **não** dispara.
- **Sub-actions:** Hooks disparam pra cada action na cadeia de invocação (parent e child).
- **Sem hooks:** Custo zero — um null check por invoke.
- **JIT:** Zero impacto. Hooks vivem no caller (`_invokeInternal`), não no código gerado.

## Handler Permissions

Controle declarativo de quais diretivas uma instância do engine pode usar. Útil pra governança (sandbox parent→child) e arquitetura (query actions não devem mutar estado).

```typescript
// Whitelist: só estes tipos permitidos
createActionEngine({
  handlers,
  allowedDirectives: ["state:query", "emit", "action"],
});

// Blacklist: estes tipos bloqueados
createActionEngine({
  handlers,
  blockedDirectives: ["state:dispatch", "emit"],
});

// Blacklist com motivações
createActionEngine({
  handlers,
  blockedDirectives: [
    { pattern: "state:dispatch", reason: "sandbox read-only", source: "parent:root" },
    { pattern: "emit", reason: "events disabled", source: "config" },
  ],
});
```

**Regras:**
- `allowedDirectives` e `blockedDirectives` são **mutuamente exclusivos**. Se ambos fornecidos: erro no constructor.
- Se nenhum fornecido: tudo permitido (default, zero overhead).
- Diretivas estáticas (`const`, `let`, `return`, `throw`) são **sempre permitidas** — não podem ser bloqueadas.
- Patterns com wildcard `*` são suportados: `"state:*"`, `"*:dispatch"`, `"*"`.

**O engine não bloqueia execução.** Permissions são config, não enforcement. Actions com handler denied **registram e executam normalmente**. Quem valida violations é o `ActionAnalyzer`. Quem decide a política (rejeitar boot, warning, ignorar) é o consumer.

```typescript
// Consultar permissões
const perms = engine.directivePermissions;

perms.get("emit");
// → { status: "available" }

perms.get("state:dispatch");
// → { status: "denied", reason: "sandbox read-only", source: "parent:root" }

// Handler inexistente: não está no mapa (unavailable é derivado)
perms.has("notify");
// → false
```

## Referência de Configuração

```typescript
interface IActionEngineConfig<TCtx> {
  handlers: HandlerInputMap<TCtx>;              // Obrigatório — handlers de diretivas
  typeField?: string;                           // Campo de dispatch (padrão: "type")
  directiveHooks?: DirectiveHooks<TCtx>;        // Hooks de diretiva
  limits?: Partial<FrameLimits>;                // maxDepth (10), maxRules, maxDirectives
  mode?: "interpret" | "jit" | "auto";          // Modo de compilação (padrão: "auto")
  autoJitThreshold?: number;                    // Auto-promote após N invocações (padrão: 8)
  allowedDirectives?: DirectivePermissionEntry[];  // Whitelist com pattern matching
  blockedDirectives?: DirectivePermissionEntry[];  // Blacklist com pattern matching
}
```

## Exports

```typescript
// Factory
import { createActionEngine } from "@statedelta-actions/actions";

// Tipos (V1)
import type {
  DirectiveHandler,
  DirectiveHandlerMap,
  DirectiveHooks,
  DirectiveExecutorFn,
  DirectiveRunnerFn,
} from "@statedelta-actions/actions";

// Tipos (V2)
import type {
  HandlerDefinition,
  HandlerAnalysis,
  ValidationResult,
  HandlerInput,
  HandlerInputMap,
  ActionDefinition,
  RegisterResult,
  RegisterError,
  RegisterWarning,
  IActionEngineConfig,
  IActionEngine,
} from "@statedelta-actions/actions";

// Lifecycle Events
import type {
  RegisterEvent,
  UnregisterEvent,
  EngineEventMap,
  EngineEventName,
} from "@statedelta-actions/actions";

// Directive Permissions
import type {
  DirectivePermissionStatus,
  DirectivePermission,
  DirectivePermissionConfig,
  DirectivePermissionEntry,
} from "@statedelta-actions/actions";

// Action Hooks
import type {
  ActionHooks,
  ActionInterceptResult,
} from "@statedelta-actions/actions";

// Register Pipeline
import { RESERVED_TYPES } from "@statedelta-actions/actions";

// Emitter
import { SimpleEmitter } from "@statedelta-actions/actions";
import type { Listener } from "@statedelta-actions/actions";

// Interpreter
import { createDirectiveInterpreter } from "@statedelta-actions/actions";

// JIT (generic)
import { buildDirectiveExecutor } from "@statedelta-actions/actions";
import type { GeneratedDirectiveExecutor } from "@statedelta-actions/actions";

// JIT (per-action)
import { buildActionExecutor } from "@statedelta-actions/actions";
import type { GeneratedActionExecutor } from "@statedelta-actions/actions";
```

## Analyzer

Funcionalidades de análise estática foram extraídas para o pacote `@statedelta-actions/analyzer`:

- **Grafo de dependências** — capabilities, leaf, maxDepth, ciclos
- **Declarations** — trust boundaries, conflitos de contrato
- **Propagators** — propriedades computadas e propagadas pelo grafo
- **Composition Control** — manifest declarativo, tags, matchers, transitividade

O analyzer consome os read-only accessors e lifecycle events do engine. Consulte a documentação do `@statedelta-actions/analyzer` para detalhes.

## Licença

MIT