beck_host/record.rs
1//! What a logged occurrence is, and what one weighs on the wire.
2//!
3//! The types a log engine stores, without the engine. `beck-rt`'s [`log`] holds the substrates —
4//! redb, SQLite, Postgres, memory — and the contract they keep; what they keep it *about* is here,
5//! because a browser tab holds a log too ([`docs/17-playground.md`](../../../../../docs/17-playground.md)
6//! §17.2) and an envelope must mean the same thing in both.
7//!
8//! [`log`]: ../../beck_rt/log/index.html
9
10use anyhow::{Context, Result};
11use beck_core::Value;
12use serde::{Deserialize, Serialize};
13
14/// Position in the total order. One totally-ordered log per application (§3.7 v1 semantics).
15pub type Seq = u64;
16
17/// Wall-clock instant, milliseconds since the Unix epoch, captured at ingress **as data**.
18///
19/// A fold may read `env.at`; it may not call a clock. The type is deliberately a plain number with
20/// no way to obtain "now" from it, so the determinism rule is hard to break by accident.
21#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
22pub struct Instant(pub i64);
23
24/// A durably logged occurrence. The fields are §3.7's, and `actor` is a stable identity — never
25/// the live `Session` capability or a token (F5).
26#[derive(Clone, Debug, PartialEq)]
27pub struct Envelope {
28 pub seq: Seq,
29 pub at: Instant,
30 pub actor: String,
31 /// The event itself.
32 ///
33 /// A `Value`, not a JSON tree. It was the latter through Phase 2, and the cost was paid on
34 /// every append *and* every read: build a `serde_json::Value`, serialise it to text, parse the
35 /// text, walk the tree back into a `Value` — four traversals and two allocations per event, at
36 /// the one point §3.7 makes serial. [`beck_core::repr`] is the encoding now; the JSON repr
37 /// stays for things a person reads.
38 pub body: Value,
39}
40
41impl Envelope {
42 /// The envelope as the `Envelope[Event]` record a fold sees.
43 pub fn to_value(&self, event: Value) -> Value {
44 beck_core::edge::envelope(self.seq, self.at.0, &self.actor, event)
45 }
46
47 /// The event. Kept as a method because every caller had one and the type changed underneath
48 /// them; there is nothing to decode any more.
49 pub fn event(&self) -> Result<Value> {
50 Ok(self.body.clone())
51 }
52
53 /// The bytes a store writes.
54 pub fn encode(&self) -> Result<Vec<u8>> {
55 let wire = Wire {
56 seq: self.seq,
57 at: self.at,
58 actor: self.actor.clone(),
59 body: beck_core::repr::Repr::of(&self.body)?,
60 };
61 Ok(postcard::to_allocvec(&wire)?)
62 }
63
64 pub fn decode(bytes: &[u8]) -> Result<Envelope> {
65 let wire: Wire = postcard::from_bytes(bytes).context("decoding a logged event")?;
66 Ok(Envelope {
67 seq: wire.seq,
68 at: wire.at,
69 actor: wire.actor,
70 body: wire.body.to_value(),
71 })
72 }
73}
74
75/// The on-disk shape of an [`Envelope`] — a concrete type, so a non-self-describing codec can
76/// encode it. See [`beck_core::repr`] for why that matters.
77#[derive(Serialize, Deserialize)]
78struct Wire {
79 seq: Seq,
80 at: Instant,
81 actor: String,
82 body: beck_core::repr::Repr,
83}
84
85/// A validated event on its way to the log, before `seq` exists.
86#[derive(Clone, Debug)]
87pub struct Pending {
88 pub at: Instant,
89 pub actor: String,
90 pub body: Value,
91}
92
93/// A snapshot of the durable fold: the accumulator plus the position it was taken at.
94#[derive(Clone, Debug)]
95pub struct Snapshot {
96 pub seq: Seq,
97 pub state: Value,
98}