Timeline
Append-only event streams with time-series analytics: named streams with retention policies and event caps, single and batch appends, time-range queries with filters and cursor pagination, bucketed counts, numeric field aggregation, structured search, and real-time WebSocket follow.
When to reach for it: activity feeds, audit trails, user behavior tracking, order and session history - any "what happened, and when" record you want to query by time window and tail in real time.
When not to: operational counters and gauges belong to Metrics; work that must be processed and acknowledged belongs to Job Queue; ephemeral broadcast with no history belongs to Pub/Sub.
Concepts
- Streams are append-only logs - events are immutable once written. There is no update or delete of an individual event; you delete the whole stream, or let retention prune old events.
- Events carry a
type(dot-namespaced by convention, likeuser.login), an arbitrary JSONdatapayload, and optional string tags. The server assigns anidand a per-streamsequencenumber. - Timestamps are Unix epoch milliseconds everywhere - event timestamps, query ranges, cursors.
- Streams auto-create on first append.
create-streamexists to set description, retention, an event cap, and tags up front - not as a precondition. - Streams are shared across the tenant - any authenticated user can list, append to, and read any stream (verified with a second user's token). There is no per-user ownership.
- Five read tools -
query(events in a window),latest(tail),count(buckets per interval),aggregate(one number per field),search(structured filters). They have their own page: Queries and analytics.
Streams
snug timeline create-stream -n docs4-activity -d "Docs demo stream" -r 30 -t env:docs
snug timeline list-streams
snug timeline stream-stats -n docs4-activity
snug timeline delete-stream -n docs4-activityStream commands take -n; event and query commands take -s. Stats show
the extent of what is stored - captured live:
{
"stream_name": "docs4-activity",
"event_count": 5,
"oldest_event": 1787893274528,
"newest_event": 1787893308689,
"size_bytes": 0
}stream-stats and delete-stream on an unknown stream fail with
404 stream_not_found. Reads and writes do not: querying a stream that
does not exist returns an empty result, and appending to one silently
creates it.
Appending events
snug timeline create-event -s docs4-activity -t user.login \
-d '{"user_id":"u123","amount":25}' --tags region:us
snug timeline batch-create -s docs4-activity -f events.jsonbatch-create reads a bare JSON array; each item takes type and data
plus optional timestamp and tags (the stream comes from -s):
[
{ "type": "order.placed", "data": { "order_id": "o-1", "amount": 30 } },
{ "type": "order.placed", "data": { "order_id": "o-2", "amount": 60 }, "tags": ["priority"] }
]Over HTTP a single append is POST /api/v1/timeline/events:
curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"stream":"docs4-activity","type":"user.login","data":{"user_id":"u123"}}' \
http://localhost:4000/api/v1/timeline/events{
"status": 201,
"msg": "Created",
"data": {
"event_id": "evt_WNcSLbZnWBPh",
"stream": "docs4-activity",
"timestamp": 1787893388879,
"sequence": 6
}
}--timestamp backfills an event at a chosen time (epoch milliseconds).
Timestamps may run at most one hour ahead of server time; beyond that the
append fails with 400 invalid_timestamp - verified with a +2h
timestamp.
Reading it back
snug timeline latest -s docs4-activity -l 10
snug timeline query -s docs4-activity --start=-1h --end now -t 'user.*' -l 50Write negative relative range values in = form: --start=-1h. The
space form --start -1h is rejected by the argument parser, which reads
-1h as a flag. Filters, wildcards, pagination, counts, aggregation, and
structured search are on the Queries and analytics page.
Following a stream in real time
follow opens a WebSocket on
GET /api/v1/timeline/streams/{stream}/follow and prints every event
appended from that moment until interrupted. Captured live, with an event
appended from a second shell (--raw shows frames verbatim):
snug timeline follow -s docs4-activity --raw{"type":"event","event":{"id":"evt_BdQKegubvFmT","stream":"docs4-activity","type":"follow.test","timestamp":1787893400766,"sequence":0,"data":{"n":1}}}One quirk, reproduced live: sequence in pushed frames is 0, not the
stored sequence number (the same event read back with latest had
sequence: 7) - identify followed events by id. Browser clients can
pass the JWT as a ?token= query parameter instead of the
Authorization header. The full message protocol is in the
Timeline API reference.
Limits and configuration
Events up to 64 KB, batches up to 1000 events, 100 streams, retention up
to 365 days (30 by default), and 1000 results per query - all tunable,
along with the automatic cleanup worker, via the TIMELINE_* variables
in the
Timeline CONFIG reference.
Reference
- Timeline API - every endpoint, callable
- Related: Metrics for operational counters, Job Queue for acknowledged work, Pub/Sub for ephemeral fanout, Live JSON for shared mutable documents