Getting started
Space Station takes JSON records for a table owned by your Carbon or Silicon account, keeps them in order, and lets you build Space Windows (live views) and Notifications on top of them.
Account → Tables → Records. A table is logical: one ClickHouse table holds everything and the
backend scopes every query to your account UUID and table ids, so FROM orders just works. Records
are whatever JSON your app cares about; plan what you send, then build views around it.
Everything here is available three ways, and they are the same thing seen from different angles: the Rust package is the interface, the CLI is a command tree over it, and this app is a subset of that.
Install the CLI and its background daemon on macOS or Linux:
silicon-apps install spacestation
Open a new terminal, then follow the commands below. For local development, see the development guide.
1. Create a table
In the app, choose Continue as Carbon for hosted Silicon Accounts sign-in, or Continue as Silicon to exchange a short-lived token from the Accounts CLI. Open Tables and create one. The account button in the sidebar keeps your saved accounts and tabs separate. Sessions survive reloads and browser restarts until they expire or you sign out.
From a terminal:
spacestation login # Carbon with a browser
# Or use your signed-in Silicon Accounts CLI:
silicon-accounts login --app spacestation -q | spacestation auth -
spacestation tables create orders
A table id is unique within your account and matches ^[a-z0-9]{1,50}$. You are shown the table key
once:
table-orders-9f3c1a… table-{table_id}-{32 hex}
It is stored hashed. There is one key per table; rotating it (spacestation tables rotate orders, or Tables → rotate key) invalidates the old one at once. Your account owns the table.
All management and query operations stay inside that owning account; see
Credentials.
Use spacestation tables retire orders to stop writes and move the table behind Retired.
Its records remain queryable and existing Space Windows keep their references. Use
spacestation tables ls --retired to find it, or tables restore orders to accept records again.
The same key works after restoration unless it was rotated. The ID remains reserved while retired.
Records already accepted before retirement may still finish flushing.
spacestation tables rm orders deletes the table and its records. The row goes at once —
the key stops working and the id is free to reuse — and the records are removed by a background
ClickHouse mutation, so a query in the next second may still see a few.
2. Send records
For a first record, use the CLI with the table key you just saved. It uses the same Rust client and daemon as your application, and does not require a login:
export SPACE_STATION_TABLE_KEY='<your table key>'
spacestation record '{"id":"o-42","customer":"ada","amount":12.5}'
# Or read one JSON object from stdin:
cat event.json | spacestation record -
For continuous recording from your application, use the published Rust package:
[dependencies]
space-station = "0.5"
silicon-apps install spacestation
# Optional development server for Space Windows:
npm i -g @teamofsilicons/space-station
use space_station::SpaceClient;
let ss = SpaceClient::new(&std::env::var("SPACE_STATION_TABLE_KEY")?)?; // account + table come from the key
ss.record(serde_json::json!({ "id": "o-42", "customer": "ada", "amount": 12.5 })); // non-blocking
record() never blocks and never panics. It stamps metadata, sanitises the value (see
limits) and hands the line to the daemon. When the in-process queue (10 000 records)
is full the record is dropped and on_error(queue_full) fires. flush() and Drop block — at
most 5 seconds — until the daemon has acked everything this client recorded,
so a one-shot program that records and exits still delivers; if the server cannot be reached in
that time flush() returns false and the lines stay in the spool for the next daemon on the
machine. Point the crate at your Space Station with SpaceClient::builder(key).url(..) or
SPACE_STATION_URL through default_url(); the default is the public host.
The same crate is how you manage everything else — tables, windows, notifications, keys — with
Auth and Space. See the Rust package.
3. The daemon
One daemon per machine carries records for any number of table keys over a single WebSocket, so
many apps and accounts on one host cost one connection. The first SpaceClient on a machine starts
it in-process (a lock file elects one); spacestation daemon run runs it in the foreground
instead, and spacestation daemon status says whether one is listening and how many records are
still unacked.
There is no local batching: records go out as soon as possible, in batches of up to 8 MB. Every
record is written to ~/.space-station/spool.jsonl before it is sent and stays there until the
server acks it, so a crash or an offline stretch loses nothing. Rejections come back per record
with a code:
| code | meaning |
|---|---|
unauthorized |
the key resolves to nothing; every record under it is dropped |
duplicate |
this record_id was seen in the last 5 minutes; treated as delivered |
size_exceeded |
the record is over the limit |
invalid |
not a JSON object |
Where a rejection surfaces depends on who is running the daemon. The server answers the
daemon, not your process: with the in-process daemon (the usual case) a rejection fires the
on_error hook of the SpaceClient that started it, as Error::Rejected { record_id, code, reason }; when a separate spacestation daemon carries your records, it is that process that
logs the rejection and counts it in spacestation daemon status, and your on_error sees
nothing — a rotated key shows up as unauthorized in the daemon's output, not in the app's. flush() follows the same line: with
the in-process daemon true means the server acked every record (rejected ones included); with a
separate daemon, which outlives your program, true means every record is in its spool. Either
way, check the daemon when the count on the server does not match.
SPACE_STATION_HOME moves ~/.space-station (the spool, the lock, auth.json, the runtime).
The daemon's unix socket is <home>/daemon.sock as long as that path fits — the OS caps a socket
path at 104 bytes on macOS and 108 on Linux — and for a home inside a deep temp or CI directory
that would exceed it, the socket moves to <system temp dir>/space-station-<hash of home>.sock
instead, so only its location changes and two homes still never share a daemon.
spacestation daemon status prints where it is.
4. What a record looks like on the server
{
"metadata": {
"record_id": "8b1e…", "table_id": "orders", "event_ts_ms": 1725000000000
},
"record": { "id": "o-42", "customer": "ada", "amount": 12.5 }
}
The library adds only the record ID, table ID, and event timestamp to metadata. It does not
collect host, device, or resource-usage information. When a batch lands, the server adds two columns:
cursor — per table, starting at 1, assigned once and never reused — and registered_ts_ms.
Rows are stored in event_ts_ms order. The Tables overview (spacestation tables overview --window 5h) measures "event → registered lag" as registered_ts_ms - event_ts_ms over the last
100 records.
5. Read it back
spacestation query "SELECT count() AS n, sum(record.amount::Float64) AS revenue FROM orders"
record and metadata are JSON columns, so cast every path you compare or sum, and note that
count() comes back as the string "42" — ClickHouse quotes every 64-bit integer; toInt32(count())
if you want a number. That and the rest of the dialect is in SQL. From there, a Space Window
turns the same query into a live view, and a Notification turns it into a
message.
Limits
All sizes are UTF-8 bytes of the JSON text as sent.
| what | limit | then |
|---|---|---|
| one string value | 32 KB | cut in the middle: abc...[SIZE]...xyz, SIZE = original bytes |
| a value that looks like a file (data URI, magic bytes, base64 blob, binary) | — | replaced by "[FILETYPE:SIZE]" |
| one record | 256 KB | refused before it leaves the process (size_exceeded) |
| one batch | 8 MB | the daemon splits; the server refuses a bigger frame |
| SiliconJSON, tool args, tool results | 64 KB | the processor run is a dev error |
Next
Space Windows · SQL · Notifications · Rust package · CLI · Credentials · HTTP API