Your First Stream
Let’s create your first event stream.
Serve
Unlike sqlite, which operates directly on the file system, xs requires a
running process to manage access to the local store. This enables features like
subscribing to real-time updates from the event stream.
Start an xs store in a dedicated window:
xs serve ./store13:35:16.868 TRACE event src/main.rs:185 Starting server with path: "./store" xs:18513:35:16.957 INFO read options=ReadOptions { follow: On, tail: false, last_id: None, limit: None } xs::store:17413:35:16.957 INFO read options=ReadOptions { follow: On, tail: true, last_id: None, limit: None } xs::store:17413:35:16.963 INFO 5ms insert_frame frame=Frame { id: "03d4por2p16i05i81fjy0fx8u", topic: "xs.start", hash: None, meta: None, ttl: None } xs::store:41013:35:16.963 0fx8u xs.start13:35:16.968 INFO read options=ReadOptions { follow: On, tail: false, last_id: None, limit: None } xs::store:174
For a long-running setup you might run xs serve ~/.local/share/cross.stream/store
under a process supervisor. This is the default location used by
xs.nu when $env.XS_ADDR isn’t set. Here we keep the demo scoped to ./store.
To point tools at another store, set XS_ADDR. This can be done temporarily with with-env:
with-env {XS_ADDR: "./store"} { .cat }Client
append command
OK! Let’s append our first event:
null | .append notes --meta {text: "a quick note"}───────┬─────────────────────────────topic │ notesid │ 03d4q1qhbiv09ovtuhokw5yxvhash │meta │ {text: "a quick note"}ttl │ forever───────┴─────────────────────────────
echo -n | xs append ./store notes --meta '{"text":"a quick note"}'{"topic":"notes","id":"03d4qic9vqkve1krajjtlbavd","hash":null,"meta":{"text":"a quick note"},"ttl":"forever"}
cat command
and then cat the stream:
.cat─#─┬──topic───┬────────────id─────────────┬─hash─┬──────────meta──────────┬───ttl───0 │ xs.start │ 03d4q1o70y6ek0ig8hwy9q00n │ │ │1 │ notes │ 03d4q1qhbiv09ovtuhokw5yxv │ │ {text: "a quick note"} │ forever───┴──────────┴───────────────────────────┴──────┴────────────────────────┴─────────
These are the raw frames on the stream. Structured data lives inline in the
meta field. We have the full expressiveness of Nushell available to us —
for example, we can read the note text directly:
.cat | last | $in.meta.texta quick note
xs cat ./store{"topic":"xs.start","id":"03d4qiab9g5vagrlrvxa2vjw0","hash":null,"meta":null,"ttl":null}{"topic":"notes","id":"03d4qic9vqkve1krajjtlbavd","hash":null,"meta":{"text":"a quick note"},"ttl":"forever"}
These are the raw frames on the stream. Structured data lives inline in the
meta field:
xs cat ./store | tail -n1 | jq -r .meta.texta quick note
.last command
Let’s submit another note:
null | .append notes --meta {text: "submit TPS report"}.cat─#─┬──topic───┬────────────id─────────────┬─hash─┬──────────────meta──────────────┬───ttl───0 │ xs.start │ 03d4q1o70y6ek0ig8hwy9q00n │ │ │1 │ notes │ 03d4q1qhbiv09ovtuhokw5yxv │ │ {text: "a quick note"} │ forever2 │ notes │ 03d4qbrxizqgav09m7hicksb0 │ │ {text: "submit TPS report"} │ forever───┴──────────┴───────────────────────────┴──────┴────────────────────────────────┴─────────
We can get the most recent note on the stream using the .last command:
.last notes───────┬─────────────────────────────────topic │ notesid │ 03d4qbrxizqgav09m7hicksb0hash │meta │ {text: "submit TPS report"}ttl │ forever───────┴─────────────────────────────────
.last notes | $in.meta.textsubmit TPS report
echo -n | xs append ./store notes --meta '{"text":"submit TPS report"}'xs cat ./store{"topic":"xs.start","id":"03d4qiab9g5vagrlrvxa2vjw0","hash":null,"meta":null,"ttl":null}{"topic":"notes","id":"03d4qic9vqkve1krajjtlbavd","hash":null,"meta":{"text":"a quick note"},"ttl":"forever"}{"topic":"notes","id":"03d4qjwnhwudlfyg1ygemmt7b","hash":null,"meta":{"text":"submit TPS report"},"ttl":"forever"}
We can get the most recent note on the stream using the last command:
xs last ./store notes{"topic":"notes","id":"03d4qjwnhwudlfyg1ygemmt7b","hash":null,"meta":{"text":"submit TPS report"},"ttl":"forever"}
xs last ./store notes | jq -r .meta.textsubmit TPS report
riffing
Finally, let’s pull a list of all our notes.
We can filter by topic and then pull out the text of each note:
.cat | where topic == "notes" | each { $in.meta.text }───┬───────────────────0 │ a quick note1 │ submit TPS report───┴───────────────────
Fun!
xs cat ./store | jq -r 'select(.topic == "notes") | .meta.text'a quick notesubmit TPS report
Fun! If you haven’t already, check out the Nushell tab. xs pairs
well with Nushell’s structured data pipeline.