# IO Plan: Events and Effects for Baba Yaga This document proposes an opinionated IO interface for embedding Baba Yaga programs into host systems. We introduce two core primitives for evented IO: - `io.listen` — subscribe to external events by name - `io.emit` — publish events (commands/effects) produced by Baba Yaga The design follows a TEA/FRP-inspired architecture: a single unidirectional data-flow where external inputs become events, user logic transforms state and produces new effects, and the host executes those effects and feeds results/errors back as new events. ## Goals - One clean, documented integration path for host systems (Node, browser, services) - Pure, testable user code: all side-effects mediated by the host via `io.emit` and `io.listen` - Deterministic core: program logic remains a function of prior state and incoming events - Easy to build SDKs/harnesses around a small surface area ## Mental Model ``` External world -> Events -> Baba Yaga Program -> Effects -> Host executes -> more Events ``` - Events are immutable data records identified by a string `topic` and a payload `data`. - Effects are commands (also a `topic` + `data`) that the host is responsible for executing. - Round-trips (request/response) are modeled as a pair of topics, e.g. `http.request` and `http.response`. ## Core API (Language-Level Semantics) The following describes the intended semantics. Implementation comes later. - `io.listen topic handler` — registers a handler function for events matching `topic`. - `topic : String` - `handler : (event: { topic: String, data: Table }) -> Unit` - Returns `Unit`. - Handlers are pure (no direct side-effects); they may call `io.emit` to request effects. - `io.emit topic data` — emits an effect (command) to the host. - `topic : String` - `data : Table | List | String | Int | Float | Bool` - Returns `Unit`. ### Event Routing and Namespacing - Topics use dotted names, e.g. `app.tick`, `db.query`, `ws.message`, `http.request`. - Conventionally, effects (commands) and events are separate namespaces; e.g. commands under `cmd.*` and events under `evt.*`, or paired `http.request`/`http.response`. ## Program Structure (TEA-flavored) We encourage a TEA-like structure: ```baba // State is a Table (author’s choice) State : type alias Table; // conceptual // Update: State x Event -> (State, Effects) update : (state, event) -> { state: State, effects: List } -> when event.topic is "app.init" then { state: { count: 0 }, effects: [] } "app.tick" then { state: { count: state.count + 1 }, effects: [] } "http.response" then { state: state, effects: [] } _ then { state: state, effects: [] }; // Subscriptions: declare external event interest init : -> io.listen "app.tick" (e -> io.emit "cmd.render" { count: 0 }); // Effect producers inside handlers handleTick : e -> io.emit "cmd.render" { count: e.data.count }; ``` Notes: - `io.listen` is declarative; the host wires it to real sources. - Handlers do not perform side-effects directly but may `io.emit` effects. ## Host SDK / Harness The host must implement a small, stable interface to embed and drive programs. Suggested host-side API (pseudo-TypeScript): ```ts type Event = { topic: string; data: unknown }; type Effect = { topic: string; data: unknown }; interface BabaProgram { // Run a program to completion on an input source; returns a controller start(initialEvents?: Event[]): ProgramController; } interface ProgramController { // Push an external event into the program dispatch(event: Event): void; // Subscribe to effects emitted by the program onEffect(subscriber: (effect: Effect) => void): () => void; // unsubscribe // Stop the program and cleanup stop(): void; } interface HostIO { // Wire program-level io.listen registrations to host event sources addListener(topic: string, handler: (event: Event) => void): () => void; // unsubscribe // Deliver effects to the host for execution deliver(effect: Effect): void; } ``` ### Responsibilities - Program side: - Calls `io.listen` to declare interests (topics and handlers). - Calls `io.emit` to request side-effects. - Host side: - Maps external sources (timers, websockets, HTTP, DB) to events and pushes them into the program via `dispatch`. - Executes effects received from `onEffect` subscribers; potentially pushes result events back (e.g., `http.response`). - Manages lifecycle (start/stop) and isolation (per session or per process). ### Startup sequence and initial effects - The host should invoke `program.start([{ topic: 'app.init', data: {...} }])` to bootstrap. - User code can `io.listen "app.init"` to initialize state and immediately `io.emit` effects. - This supports emitting to external systems as the very first action (e.g., schedule work, send a greeting, request data). Example (Baba Yaga): ```baba onInit : e -> io.emit "cmd.fetch" { url: "https://api.example.com/data" }; main : -> io.listen "app.init" onInit; ``` ### Reference Topics We recommend a small standard vocabulary: - `app.init` — sent once at startup - `app.tick` — periodic timer events - `http.request` / `http.response` - `ws.message` / `ws.send` - `db.query` / `db.result` These are guidelines; projects can add more namespaced topics. ## Example Embedding Flow (Host Pseudocode) ```ts const controller = program.start([{ topic: 'app.init', data: {} }]); controller.onEffect(async (eff) => { if (eff.topic === 'http.request') { const res = await fetch(eff.data.url, { method: eff.data.method }); controller.dispatch({ topic: 'http.response', data: { status: res.status } }); } if (eff.topic === 'cmd.render') { render(eff.data); // user-defined } }); // External source -> event ws.onmessage = (msg) => controller.dispatch({ topic: 'ws.message', data: msg }); ``` ## Testing Strategy - Treat user logic as pure functions of `(state, event) -> { state, effects }`. - Unit-test handlers and updaters by asserting emitted effects and next state. - Integration-test host harness by simulating effect execution and event feedback. ## Summary - `io.listen` and `io.emit` define a minimal, opinionated bridge between Baba Yaga and the outside world. - A TEA/FRP-inspired loop ensures purity and testability. - A small host SDK surface (dispatch/onEffect) provides a unified way to embed programs across environments. ## Application profiles (guidance) - Near real-time data processing - Sources: streams, queues, file watchers -> `evt.ingest.*` - Effects: enrichment, writes, notifications -> `cmd.write.*`, `cmd.notify.*` - Backpressure: batch events or throttle at host; program remains pure - Games / simulations - Drive time with `app.tick` at a fixed cadence - Program updates state and emits `cmd.render` or `cmd.sound` effects; host renders - WebSocket comms - Events: `ws.message` with parsed payload - Effects: `ws.send` with outbound frames; host maps to actual socket - Data transformation/search - Input events: `evt.query` or `evt.batch` - Effects: `cmd.response` with transformed payloads This single event/effect interface scales across these use cases without leaking side-effects into program logic.