pub struct EventLog<D> { /* private fields */ }Expand description
Append-only, deduplicating event log for CRDT deltas. Equality compares log data, excluding the in-memory first-admission binding flag and the in-memory collision register.
Implementations§
Source§impl<D> EventLog<D>
impl<D> EventLog<D>
Sourcepub fn new() -> Self
pub fn new() -> Self
Create an unbound log. The first accepted record binds its carrier shape.
Use for_crdt to declare a shape before any records are accepted.
Sourcepub fn with_replica_count(replica_count: usize) -> Self
pub fn with_replica_count(replica_count: usize) -> Self
Create a log for a fixed replica domain, including an empty domain.
Sourcepub fn for_crdt<C: Crdt<Delta = D>>(state: &C) -> Self
pub fn for_crdt<C: Crdt<Delta = D>>(state: &C) -> Self
Bind persistence metadata to the CRDT’s actual domain before recording.
pub fn replica_count(&self) -> Option<usize>
Sourcepub fn append<C: Crdt<Delta = D>>(
&mut self,
state: &mut C,
replica: u64,
delta: D,
) -> Result<RecordId, AppendError>where
D: PartialEq,
pub fn append<C: Crdt<Delta = D>>(
&mut self,
state: &mut C,
replica: u64,
delta: D,
) -> Result<RecordId, AppendError>where
D: PartialEq,
Allocate a fresh ID and admit a delta without applying it to state.
Returns an error if the sequence is exhausted or admission refuses the
record; in either case the log and state remain unchanged.
Sourcepub fn append_with<C, F>(
&mut self,
state: &mut C,
replica: u64,
delta: D,
apply: F,
) -> Result<RecordId, AppendError>
pub fn append_with<C, F>( &mut self, state: &mut C, replica: u64, delta: D, apply: F, ) -> Result<RecordId, AppendError>
Allocate a fresh ID, then use the same gate as incoming records.
pub fn merge_records<C: Crdt<Delta = D>, I>( &mut self, state: &C, records: I, ) -> Vec<Admission>
Sourcepub fn admit_with<C, F>(
&mut self,
state: &mut C,
record: Record<D>,
apply: F,
) -> Admission
pub fn admit_with<C, F>( &mut self, state: &mut C, record: Record<D>, apply: F, ) -> Admission
Validate the log shape and record against the destination carrier, then
check record identity. An unbound log binds only on acceptance.
Only Accepted invokes apply, with the same carrier used for validation.
An invalid fresh record leaves the log, version, and carrier unchanged.
Duplicate/Collision retain their identity verdicts, but also run validation.
A Collision also raises the collision alarm for the ID
(Self::collisions); log, version and carrier stay unchanged.
The callback must be infallible and use the same delta interpretation as
replay. This is an in-memory transition, not a crash-durability guarantee.
pub fn version(&self) -> &VersionVector
pub fn records(&self) -> &[Record<D>]
Sourcepub fn insert_record<C: Crdt<Delta = D>>(
&mut self,
state: &C,
record: Record<D>,
) -> Admissionwhere
D: PartialEq,
pub fn insert_record<C: Crdt<Delta = D>>(
&mut self,
state: &C,
record: Record<D>,
) -> Admissionwhere
D: PartialEq,
Admit without applying; state must be the carrier used for later replay.
A Collision raises the collision alarm, as in Self::admit_with.
Source§impl<D: Clone> EventLog<D>
impl<D: Clone> EventLog<D>
Sourcepub fn since(&self, version: &VersionVector) -> Vec<Record<D>>
pub fn since(&self, version: &VersionVector) -> Vec<Record<D>>
Return admitted records outside the peer’s positive contiguous prefixes and independently acknowledged sequence-zero records. By design, a permanent gap offers all later records from that writer on every exchange; deliver the missing record.
Source§impl<D> EventLog<D>
impl<D> EventLog<D>
Sourcepub fn collisions(&self) -> Vec<RecordCollision<D>>where
D: Clone,
pub fn collisions(&self) -> Vec<RecordCollision<D>>where
D: Clone,
Every held ID whose alarm is raised, in ID order, with the payload held here and the first differing payload a peer offered or reported. Empty when no fork has been seen. Durable replicas store it separately.
Sourcepub fn collision_report(&self) -> CollisionReport<D>where
D: Clone,
pub fn collision_report(&self) -> CollisionReport<D>where
D: Clone,
The report a peer needs to raise the same alarms: every entry of
Self::collisions, from this log’s perspective.
Sourcepub fn merge_collision_report(
&mut self,
report: CollisionReport<D>,
) -> Vec<(RecordId, CollisionVerdict)>where
D: PartialEq,
pub fn merge_collision_report(
&mut self,
report: CollisionReport<D>,
) -> Vec<(RecordId, CollisionVerdict)>where
D: PartialEq,
Merge a peer’s report. Each entry is judged against the payload held
here and returns its verdict in input order. Only
CollisionVerdict::Recorded changes anything, and it changes only the
alarm: never the records, version or a carrier.
Source§impl<D: Clone + WireEncode + WireSchema> EventLog<D>
impl<D: Clone + WireEncode + WireSchema> EventLog<D>
Sourcepub fn collision_report_bytes(&self) -> Result<Option<Vec<u8>>, WireError>
pub fn collision_report_bytes(&self) -> Result<Option<Vec<u8>>, WireError>
Encode Self::collision_report for the peer, or None when no alarm
is raised here. Send it back after merging a peer’s batch.
Source§impl<D: PartialEq + WireDecode + WireSchema> EventLog<D>
impl<D: PartialEq + WireDecode + WireSchema> EventLog<D>
Sourcepub fn merge_collision_report_bytes(
&mut self,
bytes: &[u8],
limits: DecodeLimits,
) -> Result<Vec<(RecordId, CollisionVerdict)>, DecodeError>
pub fn merge_collision_report_bytes( &mut self, bytes: &[u8], limits: DecodeLimits, ) -> Result<Vec<(RecordId, CollisionVerdict)>, DecodeError>
Decode a peer’s report under limits, then merge it. A decode failure
refuses the whole message and changes nothing.
Source§impl<D: WireDecode + WireSchema + PartialEq> EventLog<D>
impl<D: WireDecode + WireSchema + PartialEq> EventLog<D>
Sourcepub fn from_wire_bytes_with_limits(
bytes: &[u8],
limits: DecodeLimits,
) -> Result<Self, DecodeError>
pub fn from_wire_bytes_with_limits( bytes: &[u8], limits: DecodeLimits, ) -> Result<Self, DecodeError>
Decode an inert log with an optional top-level record budget.
Checks frame integrity and shape first, then checks the declared record
count before it reads any record. Duplicate occurrences count toward it.
Returns DecodeError::RecordLimitExceeded without a partial result.
CRC verification still scans the entire frame; callers must separately
cap input bytes and payload complexity.
As with WireDecode::from_wire_bytes, this does not validate a destination
CRDT for replay. The option is currently exposed only in Rust.
DecodeLimits::default admits at most its named record budget.
use safemesh_crdt::{DecodeError, DecodeLimits, EventLog, GSet, Record, RecordId};
let records = [Record { id: RecordId { replica: 0, sequence: 1 }, delta: GSet::<u64>::new() }];
let mut bytes = Vec::new();
EventLog::encode_records(None, &records, &mut bytes).unwrap();
assert_eq!(EventLog::<GSet<u64>>::from_wire_bytes_with_limits(
&bytes, DecodeLimits { max_records: Some(0), ..DecodeLimits::default() }),
Err(DecodeError::RecordLimitExceeded { max_records: 0 }));
let log = EventLog::<GSet<u64>>::from_wire_bytes_with_limits(
&bytes, DecodeLimits { max_records: Some(1), max_collection_elements: Some(5_000) }).unwrap();
assert_eq!(log.records().len(), 1);Sourcepub fn from_wire_bytes_for<C: Crdt<Delta = D>>(
bytes: &[u8],
state: &C,
) -> Result<Self, WireError>
pub fn from_wire_bytes_for<C: Crdt<Delta = D>>( bytes: &[u8], state: &C, ) -> Result<Self, WireError>
Decode and compare the saved shape with the destination before replay.
Plain from_wire_bytes decodes a log and retains its domain; it does not
load a CRDT. Use this method at every persisted-state loading boundary.
Sourcepub fn from_wire_bytes_for_with_limits<C: Crdt<Delta = D>>(
bytes: &[u8],
state: &C,
limits: DecodeLimits,
) -> Result<Self, DecodeError>
pub fn from_wire_bytes_for_with_limits<C: Crdt<Delta = D>>( bytes: &[u8], state: &C, limits: DecodeLimits, ) -> Result<Self, DecodeError>
Decode with record and nested collection budgets, then validate the saved shape and records against the destination before replay.
Sourcepub fn records_from_wire_bytes_for<C: Crdt<Delta = D>>(
bytes: &[u8],
state: &C,
) -> Result<Vec<Record<D>>, WireError>where
D: Clone,
pub fn records_from_wire_bytes_for<C: Crdt<Delta = D>>(
bytes: &[u8],
state: &C,
) -> Result<Vec<Record<D>>, WireError>where
D: Clone,
Decode all input occurrences in order for per-record admission reporting. Validate the entire frame, collisions and destination shape before returning. The ordinary log loaders continue to deduplicate identical records.
Sourcepub fn records_from_wire_bytes_for_with_limits<C: Crdt<Delta = D>>(
bytes: &[u8],
state: &C,
limits: DecodeLimits,
) -> Result<Vec<Record<D>>, DecodeError>where
D: Clone,
pub fn records_from_wire_bytes_for_with_limits<C: Crdt<Delta = D>>(
bytes: &[u8],
state: &C,
limits: DecodeLimits,
) -> Result<Vec<Record<D>>, DecodeError>where
D: Clone,
Decode each occurrence with record and nested collection budgets, then validate the destination before returning the occurrences.
Source§impl<D: WireDecode + WireEncode + WireSchema + PartialEq> EventLog<D>
impl<D: WireDecode + WireEncode + WireSchema + PartialEq> EventLog<D>
Sourcepub fn migrate_legacy_wire_bytes_for<C: Crdt<Delta = D>>(
bytes: &[u8],
state: &C,
) -> Result<Vec<u8>, WireError>
pub fn migrate_legacy_wire_bytes_for<C: Crdt<Delta = D>>( bytes: &[u8], state: &C, ) -> Result<Vec<u8>, WireError>
Re-encode a legacy EventLog frame (LegacyFrame) as a current shaped
frame. This is explicit; no loader ever migrates automatically.
Old frames record neither schema nor arity. The caller declares both:
the schema by choosing D, and the arity through state, a destination
carrier with the original replica count, verified independently. Do not
infer it from the largest coordinate in the log. Every record is decoded
and deduplicated as the old decoder did, then validated against state.
Record bytes and order are unchanged; only the frame gains its shape. Any
error returns no output. A current frame is validated the same way and
returned unchanged, so a repeated migration has no further effect.
Nested EventLog payloads are not rewritten; a legacy nested frame fails.
A tag 0x02 frame has no checksum. Migration accepts a frame with a changed payload bit when it still decodes, and the migrated value can differ from the original. Before migration, check your own checksum of the old file or compare it with a second copy. A tag 0x03 frame has a CRC and refuses the same kind of changed payload.
use safemesh_crdt::{EventLog, GCounter, GCounterDelta, LegacyFrame, WireDecode, WireError};
let old = [0x02, 0, 0, 0, 0]; // an empty tag 0x02 log
assert_eq!(EventLog::<GCounterDelta>::from_wire_bytes(&old),
Err(WireError::LegacyEventLogFrame { found: LegacyFrame::Tag02 }));
let state = GCounter::new(2);
let new = EventLog::migrate_legacy_wire_bytes_for(&old, &state)?;
assert_eq!(EventLog::from_wire_bytes_for(&new, &state)?.replica_count(), Some(2));Sourcepub fn migrate_legacy_wire_bytes_for_with_limits<C: Crdt<Delta = D>>(
bytes: &[u8],
state: &C,
limits: DecodeLimits,
) -> Result<Vec<u8>, DecodeError>
pub fn migrate_legacy_wire_bytes_for_with_limits<C: Crdt<Delta = D>>( bytes: &[u8], state: &C, limits: DecodeLimits, ) -> Result<Vec<u8>, DecodeError>
Migrate a legacy frame with a budget for each input record occurrence.
Duplicate records count; the limit is checked before decoding the next record.
A current shaped frame uses the same limited decoder as ordinary loading.
The tag 0x02 checksum warning and pre-migration checks in
Self::migrate_legacy_wire_bytes_for apply here too.
Source§impl<D: WireEncode + WireSchema> EventLog<D>
impl<D: WireEncode + WireSchema> EventLog<D>
Sourcepub fn encode_records(
replica_count: Option<usize>,
records: &[Record<D>],
out: &mut Vec<u8>,
) -> Result<(), WireError>
pub fn encode_records( replica_count: Option<usize>, records: &[Record<D>], out: &mut Vec<u8>, ) -> Result<(), WireError>
Encode an inert batch without admitting it into a live log.
This preserves input occurrences, including duplicates and invalid records.
Receivers must use records_from_wire_bytes_for or from_wire_bytes_for
before applying the payload. Encoding does not validate a CRDT domain.
Trait Implementations§
Source§impl<D: WireDecode + WireSchema + PartialEq> WireDecode for EventLog<D>
impl<D: WireDecode + WireSchema + PartialEq> WireDecode for EventLog<D>
fn decode_wire(cursor: &mut WireCursor<'_>) -> Result<Self, WireError>
Source§fn decode_wire_with_collection_limits(
cursor: &mut WireCursor<'_>,
limits: CollectionLimits,
) -> Result<Self, WireError>
fn decode_wire_with_collection_limits( cursor: &mut WireCursor<'_>, limits: CollectionLimits, ) -> Result<Self, WireError>
Source§fn decode_wire_with_limits(
cursor: &mut WireCursor<'_>,
limits: CollectionLimits,
max_records: Option<usize>,
) -> Result<Self, WireError>
fn decode_wire_with_limits( cursor: &mut WireCursor<'_>, limits: CollectionLimits, max_records: Option<usize>, ) -> Result<Self, WireError>
Source§fn from_wire_bytes_with_collection_limits(
bytes: &[u8],
limits: CollectionLimits,
) -> Result<Self, WireError>
fn from_wire_bytes_with_collection_limits( bytes: &[u8], limits: CollectionLimits, ) -> Result<Self, WireError>
fn from_wire_bytes(bytes: &[u8]) -> Result<Self, WireError>
Source§impl<D: WireEncode + WireSchema> WireEncode for EventLog<D>
impl<D: WireEncode + WireSchema> WireEncode for EventLog<D>
fn encode_wire(&self, out: &mut Vec<u8>) -> Result<(), WireError>
fn to_wire_bytes(&self) -> Result<Vec<u8>, WireError>
Source§impl<D: WireSchema> WireSchema for EventLog<D>
impl<D: WireSchema> WireSchema for EventLog<D>
Source§fn wire_schema() -> Cow<'static, [u8]>
fn wire_schema() -> Cow<'static, [u8]>
const REQUIRES_ARITY: bool = false
impl<D: Eq> Eq for EventLog<D>
Auto Trait Implementations§
impl<D> Freeze for EventLog<D>
impl<D> RefUnwindSafe for EventLog<D>where
D: RefUnwindSafe,
impl<D> Send for EventLog<D>where
D: Send,
impl<D> Sync for EventLog<D>where
D: Sync,
impl<D> Unpin for EventLog<D>where
D: Unpin,
impl<D> UnsafeUnpin for EventLog<D>
impl<D> UnwindSafe for EventLog<D>where
D: RefUnwindSafe + UnwindSafe,
Blanket Implementations§
Source§impl<T> Any for Twhere
T: 'static + ?Sized,
impl<T> Any for Twhere
T: 'static + ?Sized,
Source§fn type_id(&self) -> TypeId
fn type_id(&self) -> TypeId
TypeId of self. Read moreSource§impl<T> Borrow<T> for Twhere
T: ?Sized,
impl<T> Borrow<T> for Twhere
T: ?Sized,
Source§fn borrow(&self) -> &T
fn borrow(&self) -> &T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§unsafe fn clone_to_uninit(&self, dest: *mut u8)
unsafe fn clone_to_uninit(&self, dest: *mut u8)
clone_to_uninit)self to dest. Read moreSource§impl<T, U> Into<U> for Twhere
U: From<T>,
impl<T, U> Into<U> for Twhere
U: From<T>,
Source§fn into(self) -> U
fn into(self) -> U
Calls U::from(self).
That is, this conversion is whatever the implementation of
From<T> for U chooses to do.