about summary refs log tree commit diff stats
path: root/js/baba-yaga/scratch/docs/IO.md
blob: 6399b665e516822b0f0da30d26067d657a75797f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# 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.