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}