|
| 1 | +# Architecture |
| 2 | + |
| 3 | +flowcontrol is one engine with many surfaces. The design goal: **one data model, one schema, |
| 4 | +one set of generated bindings**, reachable natively, from Node, and from the browser. |
| 5 | + |
| 6 | +## flowcore — the engine |
| 7 | + |
| 8 | +`flowcore` (Rust) holds all the logic: the node tree, dependency evaluation, the status |
| 9 | +cascade. It talks to storage through a narrow **`Sql` seam** (execute / query / transaction) |
| 10 | +rather than a concrete database, so the same engine compiles two ways: |
| 11 | + |
| 12 | +- **native** — over `rusqlite`, for a native daemon; |
| 13 | +- **wasm32** — pure Rust, no C toolchain, calling back into a **host** for SQL |
| 14 | + (`__flowHostExec` / `__flowHostQuery`). |
| 15 | + |
| 16 | +That host-import design is what lets the identical engine run inside a Node daemon _and_ |
| 17 | +directly in a browser tab. |
| 18 | + |
| 19 | +## One proto, one dispatch |
| 20 | + |
| 21 | +A single protobuf definition generates the bindings for every language (TypeScript, Go, Rust). |
| 22 | +The engine exposes one synchronous `dispatch()` entry point, so a "mutation" is the same shape |
| 23 | +whether it arrives from the web board, the CLI, or an agent. |
| 24 | + |
| 25 | +## The daemon |
| 26 | + |
| 27 | +`flowd` (and its Node twin, `flowd.js`) is the **single writer** to one SQLite file. It serves |
| 28 | +the task-graph API over **gRPC-web/Connect on HTTP/1.1**, bound to `127.0.0.1`. The web board |
| 29 | +is served from the same origin, which removes CORS, mixed-content, and certificate concerns in |
| 30 | +one move. A **bearer token** (in `~/.flowcontrol/session.json`, mode 0600) guards the RPC |
| 31 | +paths; the daemon also enforces a `Host` allow-list against DNS-rebinding. |
| 32 | + |
| 33 | +## Lifecycle — the store, not the daemon |
| 34 | + |
| 35 | +The daemon is disposable; **SQLite is the source of truth**. Every client runs the same |
| 36 | +_ensure-on-connect_: connect to the daemon if one is up (discovered via `session.json`, guarded |
| 37 | +by a single-instance lock), else spawn one. If the daemon dies, no data is lost — the next |
| 38 | +client brings it back. Persistence is emergent: whichever client's process isn't torn down |
| 39 | +keeps the daemon alive; when it goes, the data stays on disk. |
| 40 | + |
| 41 | +## The browser |
| 42 | + |
| 43 | +The standalone web build runs `flowcore` as wasm in a **Web Worker**, persisting to **OPFS** |
| 44 | +via `@sqlite.org/sqlite-wasm` (a durable SAHPool VFS). It is the real engine over real SQLite — |
| 45 | +fully functional, private to the browser, no network — which is what powers the GitHub Pages |
| 46 | +demo. |
| 47 | + |
| 48 | +## Transport, everywhere the same |
| 49 | + |
| 50 | +Browser (`connect-web`), agent server (`connect-node`), and CLI (`connect-go`) all speak the |
| 51 | +same gRPC-web/Connect dialect to the same loopback daemon. One transport choice keeps the |
| 52 | +clients thin and makes a future hosted, multi-tenant deployment (the `Sql` seam over a cloud |
| 53 | +database, keyed by a `workspace`) a natural extension rather than a rewrite. |
0 commit comments