OxL Reference

module std.topic

std.topic — topics: a durable, partitioned, replicated log with broadcast subscribers and consumer groups (§10.5, §11.2, D-4900..D-4919).

oxl
@retain(7d)
topic OrderEvents: OrderEvent sharded by .userId

emit OrderEvents(e)                                              // OrderEvents.emit(e): panics when the topic is unreachable
OrderEvents.publish(e)!                                          // returns the error instead
for await e in OrderEvents.subscribe(from: .Latest) { … }        // broadcast: every subscriber sees every element
for await e in OrderEvents.subscribeKey(uid) { … }               // only the elements with this sharding key
for await e in OrderEvents.subscribe(group: "indexer") { … }     // a queue: one member of the group gets each element
atomic(db) { db.insert(order); emit OrderEvents(e) }             // the event goes out after the commit (outbox)

The compiler turns every topic declaration into a static of a generated type with these methods (compiler/src/sema/TopicSema.cpp); sharded by .field gives the partition key of publish/emit. The functions of this module are what the generated methods call. Elements are stored with std.ser (Format.Oxl). A partition is the key prefix m/{topic#p}/ in the node's std.kv store __topics (one shard, so offsets, cursors and leases of a partition change in one Raft batch); hash(key) % partitions picks the partition (one partition without sharded by), offsets are assigned by the shard, per partition, gapless and monotonic. Order is guaranteed per key. Delivery is at-least-once.

A group member holds an element for lease (30 s): the element is acknowledged when the loop asks for the NEXT one, so a body that finishes normally acks, a body that panics, returns or is cancelled does not and the element is redelivered after the lease. Runtime: runtime/src/topic/topic_api.cpp over topic_store.cpp. TopicError and TopicStart (the from: values) are prelude types (std/core/topic.oxl).

Example:

oxl✓ checked in CI
data Job { id: i64 }

topic Jobs: Job

cluster demo {
  role store { journal }
}

fn main() -> () | TopicError io, db {
  Jobs.publish(Job{ id: 1 })?
  for await j in Jobs.subscribe(from: .Earliest) {
    print("job {j.id}")
    break
  }
}
Output
job 1

Functions

auditThe system topic oxl.audit (§13.4)
countOfThe number of elements the topic still holds (retention removes the older ones).
declareDeclares a topic (idempotent by name) and returns its handle.
emitCondOfemitOf for a topic whose policy has conditions (bits as for publishCondOf).
emitInCondOfemitInOf for a topic whose policy has conditions (bits as for publishCondOf); the policy is checked before the element goes to the outbox.
emitInOfemit inside atomic(db)
emitOfemit outside a transaction
keyBytesThe partition key bytes of a value (sharded by .field).
noKeyThe key of a topic without sharded by.
openDecrypts what seal produced for the same topic and field.
openBytesReverses sealBytes.
openStrReverses sealStr.
publishCondOfpublishOf for a topic whose policy has conditions
publishOfStores one element of a topic and returns when the store has accepted it.
sealEncrypts one field of a topic element (@encrypted fields, §13.3)
sealBytesseal for a bytes field; the generated code of an @encrypted field calls it.
sealStrseal for a str field; the generated code of an @encrypted field calls it.
subscribeCondOfsubscribeOf for a topic whose policy has conditions (bits
subscribeKeyCondOfsubscribeKeyOf for a topic whose policy has conditions (bits
subscribeKeyOfA broadcast subscription limited to the elements with one sharding key.
subscribeOfOpens the subscription NOW (so .Latest means "from this call", not "from the first pull") and returns the stream that reads it.
sweepOfApplies the retention of the topic now (@retain(...)) and returns the number of elements removed.
unsealopen under a name that use std.topic { …

See also