# 04 — Events and Lifecycle

Builds on [01-keyword-search](../01-keyword-search/) by demonstrating the engine's event system and incremental refresh.

## What's new

- **EventBus** — subscribe to engine lifecycle events with glob patterns
- **Incremental refresh** — add and remove documents without full re-indexing
- **MutableFetcher** — a `SchemaFetcher` that supports `diff()` for incremental updates
- **Engine state machine** — CREATED → READY → DESTROYED

## Key concepts

### Event subscriptions

The `InMemoryEventBus` supports glob-pattern subscriptions:

```ts
eventBus.on("engine.*", (event) => {
  /* all engine events */
});
eventBus.on(SearchEventType.POLL_COMPLETED, (event) => {
  /* refresh done */
});
eventBus.on(SearchEventType.POLL_SKIPPED, (event) => {
  /* no changes */
});
```

Events are useful for monitoring (logging, metrics), not for control flow. The engine operates the same whether an event bus is attached or not.

### Incremental refresh via diff()

Instead of re-fetching and re-indexing the entire corpus on every refresh, `SchemaFetcher.diff()` returns only what changed:

```ts
interface SchemaDiff<T> {
  added: T[];
  changed: T[];
  removed: string[]; // IDs to remove
}
```

When `diff()` returns `null`, the engine skips the refresh entirely and emits `POLL_SKIPPED`. This is the common case — most refresh cycles have no changes.

### Lifecycle walkthrough

The example demonstrates:

1. **Init** — engine transitions CREATED → READY, indexes 2 documents
2. **Search miss** — "payment" returns 0 results (not in corpus yet)
3. **Add + refresh** — `fetcher.addItem(...)` then `engine.refresh()` — 1 document added
4. **Search hit** — "payment" now returns the new document
5. **No-op refresh** — `engine.refresh()` with no pending diff — emits POLL_SKIPPED
6. **Remove + refresh** — `fetcher.removeItem("doc-1")` then `engine.refresh()` — 1 document removed
7. **Destroy** — engine transitions READY → DESTROYED, releases resources

## Running

```bash
npx tsx examples/04-events-and-lifecycle/main.ts
```

## Expected output

```
=== Init ===
  [event] engine.init.started
  [event] engine.init.completed
  Documents: 2
  Ready: true

=== Search: 'payment' (before adding) ===
  Results: 2

=== Refresh: adding 'Payment Ledger' ===
  [event] refresh completed in 6ms
  Documents: 3

=== Search: 'payment' (after adding) ===
  0.0742  doc-3: Payment Ledger
  0.0385  doc-1: Revenue Report
  0.0370  doc-2: Artist Catalog

=== Refresh: no changes ===
  [event] refresh skipped: no changes

=== Refresh: removing doc-1 ===
  [event] refresh completed in 0ms
  Documents: 2

=== Destroy ===
  [event] engine.destroy.started
  [event] engine.destroy.completed
  Ready: false
```
