Anatomy of a Machine
What happens when you run a machine — the server, API, WebSocket, database, and CLI flags.
What the CLI does
When you run vintage serve my-machine.ts, the CLI:
- Loads your file and detects whether it exports a single machine class or
a
defineConfig()setup - Instantiates each machine and calls
init() - Compiles any custom UI bundles (if provided)
- Starts a HTTP server with a REST API, WebSocket endpoint, an in-memory SQLite database (or a file if configured) and the web UI
- Connects to the cloud, if configured
The server runs until you stop it, at which point kill() is called on each
machine.
What gets exposed
Every machine gets its own API namespace under /api/:machineId/:
| Endpoint | Description |
|---|---|
GET /api/:machineId/state | Current state (underscore-prefixed keys stripped) |
POST /api/:machineId/invoke/:name | Invoke a named action with a JSON body |
GET /api/:machineId/functions | List all actions with their schemas |
GET /api/:machineId/config | The machine's current config |
POST /api/:machineId/restart | Kill and reinitialise the machine |
GET /api/system/setup | All machines — state, actions, custom UI URLs |
GET /api/ws | WebSocket connection |
The machine ID is derived from __NAME — lowercased, spaces replaced with
hyphens, non-alphanumeric characters removed. In multi-machine mode, the key
from defineConfig is used as-is.
WebSocket
Connect to GET /api/ws to receive real-time messages from all machines. Each
message is a JSON object:
type ServerMessage =
| { type: "state"; machineId: string; data: Record<string, unknown> }
| { type: "info"; machineId: string; data: string }
| { type: "warn"; machineId: string; data: string }
| { type: "error"; machineId: string; data: string }
| {
type: "event"
machineId: string
data: { event: string; data: Record<string, unknown> }
}state messages are sent on every updateState() call. info, warn, and
error messages come from emitInfo(), emitWarn(), and emitError()
respectively.
CLI flags
--port / -p
vintage serve my-machine.ts --port 4000Sets the HTTP server port. Defaults to 4000.
--db <path>
vintage serve my-machine.ts --db ./data.dbNormally the vintage cli uses an in-memory SQLite database. The history API works normally during the session, but all data is lost on shutdown, which is useful for development or ephemeral test runs.
Enables a local SQLite database for state history. Every updateState() call
writes a timestamped row for each key.
GET /api/history?machineId=X&stateKey=Y&from=N&to=M| Query param | Description |
|---|---|
machineId | Filter by machine (optional) |
stateKey | Filter by state key name (optional) |
from | Start timestamp in ms (optional, requires to) |
to | End timestamp in ms (optional, requires from) |
--overwrite-db
vintage serve my-machine.ts --db ./data.db --overwrite-dbDrops and recreates the database table on startup. Use this to clear accumulated
history without deleting the file manually. Has no effect without --db.
--ui <path>
vintage serve my-machine.ts --ui ./ui/index.tsxLoads a custom React UI for the machine. The file is bundled with Bun at startup
and served from /ui/:machineId/bundle.js. Only valid in single-machine mode —
in multi-machine mode, specify ui per machine inside defineConfig.
See Custom UIs for details on writing UI components.
Local database schema
When --db is enabled, each state key-value pair is stored as a separate row:
| Column | Type | Description |
|---|---|---|
id | integer | Auto-incrementing primary key |
key | text | State key name (e.g. temperature) |
value | text | JSON-serialised value |
machineId | text | Machine ID |
session | text | UUID generated at server startup — groups rows from the same run |
timestamp | integer | Date.now() at the time of the update |
A single updateState({ temperature: 23, humidity: 60 }) call produces two rows
— one per key — with the same timestamp and session.
Cloud streaming
If the following environment variables are set, state changes and log messages are streamed to a Vintage Cloud bucket in real time:
export VINTAGE_CLOUD_PUBLIC_ID=your-bucket-id
export VINTAGE_CLOUD_WRITE_TOKEN=your-write-token
vintage serve my-machine.tsCloud streaming runs alongside the local database — both can be active at the same time. See Cloud for how to create a bucket and read the data.