npm package: @statedelta-actions/events
Description: Event processing engine with listener dispatch and JIT optimization
Author: Anderson D. Rosa
Latest version: 0.3.0

# EventProcessor — Eventos como Actions Deferidas

> **Módulo:** `events/`
> **Escopo:** Dispatcher de listeners sobre o ActionEngine. Listeners reagem a eventos nomeados, executando diretivas via hidden actions.
> **Dependências:** `core/` (tipos, slot analysis), `actions/` (IActionEngine). Zero dependência de `rules/`.
>
> Para arquitetura interna, pipelines e fluxos, ver [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md).
> Para decisões arquiteturais, ver [`docs/DECISIONS.md`](./docs/DECISIONS.md).

---

## O que é

O `EventProcessor` é um dispatcher de listeners sobre o ActionEngine. Quando o consumer emite `{ event: "save" }`, o processor resolve quais listeners escutam "save", monta params, e delega a execução pro ActionEngine via `invoke()`.

**Evento = action deferida.** A única diferença entre action e evento é **quando** executa. A infraestrutura é idêntica — registro, params, directives, invoke. O EventProcessor não executa diretivas ele mesmo.

**Listeners viram hidden actions.** Cada listener registrado cria uma action `event:{id}` no ActionEngine. Isso significa que listeners coexistem no mesmo grafo com actions diretas e rules (`rule:{id}`). O analyzer valida tudo junto — ciclos, capabilities, composition control.

**Sem condição, sem cascade.** Listeners não têm `when()` (match por nome, não por expressão) nem sub-rules (dispatch direto, sem recursão). Chegou evento, matched, executa.

**Halt é scoped ao evento.** Halt em um listener para apenas os listeners daquele evento. O próximo evento na fila continua normalmente.

---

## EventListenerDefinition — O que o consumer fornece

```typescript
interface EventListenerDefinition<TCtx> {
  readonly id: string;
  readonly priority: number;
  readonly on: string | string[];
  readonly then: readonly Directive<TCtx>[];
  readonly tags?: readonly string[];
  readonly declarations?: Record<string, unknown>;
  readonly metadata?: Record<string, unknown>;
}
```

| Campo | Semântica |
|-------|-----------|
| `id` | Identificador único do listener |
| `priority` | Ordem de execução (desc — maior executa primeiro) |
| `on` | Evento(s) que o listener escuta. String ou array |
| `then` | Diretivas executadas quando matched |
| `tags` | Tags extras (além de `"event"` que é injetado automaticamente) |
| `declarations` | Contratos públicos passados pro grafo (trust boundaries) |
| `metadata` | Dados opacos armazenados na definition |

---

## API

### Construction

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

const ep = createEventProcessor({
  actionEngine,                // IActionEngine — obrigatório
  eventHooks: { ... },         // EventHooks — opcional
  middleware: [ ... ],         // EventMiddleware[] — opcional
  mode: "auto",               // "interpret" | "jit" | "auto" (default: "auto")
  autoJitThreshold: 8,        // Chamadas antes de auto-promote (default: 8)
});
```

### Registration

```typescript
// Registrar listeners
const result = ep.register([
  {
    id: "on-save",
    priority: 100,
    on: "save",
    then: [{ type: "state", target: "hp", value: 10 }],
  },
  {
    id: "on-damage",
    priority: 200,
    on: ["damage", "combat"],   // multi-event
    then: [{ type: "state", target: "shield", value: -1 }],
  },
]);

// result: { registered: string[], errors: EventRegisterError[], warnings: RegisterWarning[] }

// Remover listener
ep.unregister("on-save");  // true se existia, false se não
```

### Processing

```typescript
// Sync (throws se isAsync = true)
const result = ep.processEvents(
  [{ event: "save" }, { event: "damage", data: { amount: 10 } }],
  ctx,
);

// Async (sempre funciona)
const result = await ep.processEventsAsync(
  [{ event: "save" }],
  ctx,
);
```

### Batch

```typescript
ep.beginBatch();
ep.register(listeners1);
ep.register(listeners2);
const result = ep.endBatch();
// Suporta nesting — inner endBatch é no-op, outer processa tudo
```

### Compilation

```typescript
// Força promoção pra JIT (no-op se já em JIT ou mode = "interpret")
ep.compile();

// Consultar modo atual
ep.compilationMode;  // "interpret" | "jit"
ep.isAsync;          // true se hooks ou actionEngine são async
```

---

## Hooks — EventHooks

```typescript
interface EventHooks<TCtx> {
  beforeEvent?: (listener, evalCtx) => "skip" | "abort" | void;
  afterEvent?:  (listener, result, evalCtx) => "abort" | void;
  onEventsComplete?: (result) => void;
}
```

| Hook | Quando | Retornos |
|------|--------|----------|
| `beforeEvent` | Antes de cada listener | `"skip"` pula listener, `"abort"` para evento |
| `afterEvent` | Após invoke de cada listener | `"abort"` para evento |
| `onEventsComplete` | Após processar todos os eventos | fire-and-forget |

Hooks ausentes não têm custo — nem no interpreter nem no JIT.

### Exemplo: disable via hook

```typescript
const disabled = new Set<string>();

const ep = createEventProcessor({
  actionEngine,
  eventHooks: {
    beforeEvent: (listener) => {
      if (disabled.has(listener.id)) return "skip";
    },
  },
});

disabled.add("on-save");   // "disable"
disabled.delete("on-save"); // "enable"
```

---

## Middleware

```typescript
type EventMiddleware<TCtx> = (
  listener: EventListenerDefinition<TCtx>,
  ctx: TCtx,
  params: Record<string, unknown>,
) => Record<string, unknown>;
```

Middleware é pipeline de **enriquecimento de dados**. Não afeta fluxo de controle (sem "skip", sem "abort"). Cada middleware recebe e retorna params.

```typescript
const ep = createEventProcessor({
  actionEngine,
  middleware: [
    (listener, ctx, params) => ({
      ...params,
      timestamp: Date.now(),
      source: listener.id,
    }),
  ],
});
```

Sem middleware, `$event` chega direto no scope da action. Com middleware, `$event` entra como base e o middleware enriquece.

---

## $event Injection

Todo listener recebe automaticamente no scope:

```typescript
{ $event: { type: "save", data: eventData } }
```

Acessível dentro de handlers via `frame.scope.$event`.

---

## Results

```typescript
interface EventProcessingResult {
  readonly eventResults: readonly SingleEventResult[];
  readonly unprocessedEvents: readonly string[];   // eventos sem listeners
  readonly errors: readonly RuleError[];
  readonly counters: FrameCounters;
  readonly totalEvents: number;
  readonly processedEvents: number;
}

interface SingleEventResult {
  readonly event: string;
  readonly matched: readonly number[];
  readonly skipped: readonly number[];
  readonly notMatched: readonly number[];
  readonly errors: readonly RuleError[];
  readonly halted: boolean;
  readonly haltedBy?: string;
  readonly counters: FrameCounters;
}

interface EventRegisterResult {
  readonly registered: readonly string[];
  readonly errors: readonly EventRegisterError[];
  readonly warnings: readonly RegisterWarning[];
}
```

---

## Compilation — 3 Modos

| Modo | Comportamento |
|------|--------------|
| `"interpret"` | Interpreter sempre. Sem promoção |
| `"jit"` | JIT imediato no constructor |
| `"auto"` (default) | Interpreter inicial. Promove pra JIT após `autoJitThreshold` chamadas |

O JIT gera código equivalente ao interpreter via `new Function()`, eliminando branches de hooks/middleware ausentes em compile-time. O consumer não precisa se preocupar — `"auto"` é o default e promove transparentemente.