Vintage Machine Works

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:

  1. Loads your file and detects whether it exports a single machine class or a defineConfig() setup
  2. Instantiates each machine and calls init()
  3. Compiles any custom UI bundles (if provided)
  4. Starts a HTTP server with a REST API, WebSocket endpoint, an in-memory SQLite database (or a file if configured) and the web UI
  5. 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/:

EndpointDescription
GET /api/:machineId/stateCurrent state (underscore-prefixed keys stripped)
POST /api/:machineId/invoke/:nameInvoke a named action with a JSON body
GET /api/:machineId/functionsList all actions with their schemas
GET /api/:machineId/configThe machine's current config
POST /api/:machineId/restartKill and reinitialise the machine
GET /api/system/setupAll machines — state, actions, custom UI URLs
GET /api/wsWebSocket 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 4000

Sets the HTTP server port. Defaults to 4000.


--db <path>

vintage serve my-machine.ts --db ./data.db

Normally 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 paramDescription
machineIdFilter by machine (optional)
stateKeyFilter by state key name (optional)
fromStart timestamp in ms (optional, requires to)
toEnd timestamp in ms (optional, requires from)

--overwrite-db

vintage serve my-machine.ts --db ./data.db --overwrite-db

Drops 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.tsx

Loads 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:

ColumnTypeDescription
idintegerAuto-incrementing primary key
keytextState key name (e.g. temperature)
valuetextJSON-serialised value
machineIdtextMachine ID
sessiontextUUID generated at server startup — groups rows from the same run
timestampintegerDate.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.ts

Cloud 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.

On this page