pub struct ShardManager { /* private fields */ }Expand description
Manages time-sharded event files in a .bones repository.
The shard manager handles:
- Directory initialization
- Shard rotation on month boundaries
- Atomic append with advisory locking
- Monotonic clock maintenance
- Torn-write recovery
- Replay (reading all shards chronologically)
- Sealed shard manifest generation
Implementations§
Source§impl ShardManager
impl ShardManager
Sourcepub fn new(bones_dir: impl Into<PathBuf>) -> Self
pub fn new(bones_dir: impl Into<PathBuf>) -> Self
Create a new ShardManager for the given .bones directory.
Does not create directories on construction; call init
or ensure_dirs first if needed.
Sourcepub fn events_dir(&self) -> PathBuf
pub fn events_dir(&self) -> PathBuf
Path to the events directory.
Sourcepub fn clock_path(&self) -> PathBuf
pub fn clock_path(&self) -> PathBuf
Path to the monotonic clock file.
Sourcepub fn shard_filename(year: i32, month: u32) -> String
pub fn shard_filename(year: i32, month: u32) -> String
Generate the shard filename for a given year and month.
Sourcepub fn shard_path(&self, year: i32, month: u32) -> PathBuf
pub fn shard_path(&self, year: i32, month: u32) -> PathBuf
Path to a specific shard file.
Sourcepub fn manifest_path(&self, year: i32, month: u32) -> PathBuf
pub fn manifest_path(&self, year: i32, month: u32) -> PathBuf
Path to a manifest file for a given shard.
Sourcepub fn ensure_dirs(&self) -> Result<(), ShardError>
pub fn ensure_dirs(&self) -> Result<(), ShardError>
Create the .bones/events/ and .bones/cache/ directories if they
don’t exist. Idempotent.
§Errors
Returns ShardError::InitFailed if directory creation fails.
Sourcepub fn init(&self) -> Result<(i32, u32), ShardError>
pub fn init(&self) -> Result<(i32, u32), ShardError>
Initialize the shard directory and create the first shard file with the standard header if no shards exist.
Returns the (year, month) of the active shard.
§Errors
Returns ShardError on I/O failure or if directories cannot be
created.
Sourcepub fn list_shards(&self) -> Result<Vec<(i32, u32)>, ShardError>
pub fn list_shards(&self) -> Result<Vec<(i32, u32)>, ShardError>
List all shard files in chronological order as (year, month) pairs.
Shard filenames must match YYYY-MM.events. Invalid filenames are
silently skipped.
§Errors
Returns ShardError::Io if the directory cannot be read.
Sourcepub fn active_shard(&self) -> Result<Option<(i32, u32)>, ShardError>
pub fn active_shard(&self) -> Result<Option<(i32, u32)>, ShardError>
Get the active (most recent) shard, if any.
§Errors
Returns ShardError::Io if the directory cannot be read.
Sourcepub fn create_shard(&self, year: i32, month: u32) -> Result<PathBuf, ShardError>
pub fn create_shard(&self, year: i32, month: u32) -> Result<PathBuf, ShardError>
Create a new shard file with the standard header.
Returns the path of the created file. Does nothing if the file already exists.
§Errors
Returns ShardError::Io if the file cannot be written.
Sourcepub fn rotate_if_needed(&self) -> Result<(i32, u32), ShardError>
pub fn rotate_if_needed(&self) -> Result<(i32, u32), ShardError>
Check if the current month differs from the active shard’s month. If so, seal the old shard (generate manifest) and create a new one.
Returns the (year, month) of the now-active shard.
§Errors
Returns ShardError on I/O failure during rotation.
Sourcepub fn write_manifest(
&self,
year: i32,
month: u32,
) -> Result<ShardManifest, ShardError>
pub fn write_manifest( &self, year: i32, month: u32, ) -> Result<ShardManifest, ShardError>
Generate and write a manifest file for a sealed shard.
§Errors
Returns ShardError::Io if the shard file cannot be read or the
manifest cannot be written.
Sourcepub fn read_manifest(
&self,
year: i32,
month: u32,
) -> Result<Option<ShardManifest>, ShardError>
pub fn read_manifest( &self, year: i32, month: u32, ) -> Result<Option<ShardManifest>, ShardError>
Read a manifest file if it exists.
§Errors
Returns ShardError::Io if the manifest file exists but cannot
be read.
Sourcepub fn validate_sealed_shards(
&self,
) -> Result<Vec<ShardIntegrityIssue>, ShardError>
pub fn validate_sealed_shards( &self, ) -> Result<Vec<ShardIntegrityIssue>, ShardError>
Validate byte-length of sealed shards against their manifests.
For each shard except the last (active), checks whether a .manifest
file exists and whether byte_len matches the actual file size on
disk. This is a lightweight startup check — full BLAKE3 hash
verification is deferred to bn doctor / bn verify.
Returns a list of issues found. An empty list means all sealed shards with manifests match.
§Errors
Returns ShardError::Io if shard files or manifests cannot be read.
Sourcepub fn append(
&self,
line: &str,
durable: bool,
lock_timeout: Duration,
) -> Result<i64, ShardError>
pub fn append( &self, line: &str, durable: bool, lock_timeout: Duration, ) -> Result<i64, ShardError>
Append an event line to the active shard.
This method:
- Acquires the repo-wide advisory lock.
- Rotates shards if the month has changed.
- Reads and updates the monotonic clock.
- Appends the line using
O_APPEND+write_all+flush. - Optionally calls
sync_dataifdurableis true. - Releases the lock.
The line must be a complete TSJSON line ending with \n.
Returns the monotonic timestamp used.
§Errors
Returns ShardError::Lock if the lock cannot be acquired within
lock_timeout, or ShardError::Io on write failure.
Sourcepub fn append_raw(
&self,
year: i32,
month: u32,
line: &str,
) -> Result<(), ShardError>
pub fn append_raw( &self, year: i32, month: u32, line: &str, ) -> Result<(), ShardError>
Append a raw line without locking or clock update.
Used internally and in tests. The caller is responsible for holding the lock and managing the clock.
§Errors
Returns ShardError::Io on write failure.
Sourcepub fn read_clock(&self) -> Result<i64, ShardError>
pub fn read_clock(&self) -> Result<i64, ShardError>
Read the current monotonic clock value.
Returns 0 if the clock file doesn’t exist.
§Errors
Returns ShardError::Io if the clock file exists but cannot be
read.
Sourcepub fn next_timestamp(&self) -> Result<i64, ShardError>
pub fn next_timestamp(&self) -> Result<i64, ShardError>
Compute the next monotonic timestamp and write it to the clock file.
next = max(system_time_us, last + 1)
The caller must hold the repo lock.
§Errors
Returns ShardError::Io if the clock file cannot be read or
written.
Sourcepub fn recover_torn_writes(&self) -> Result<Option<u64>, ShardError>
pub fn recover_torn_writes(&self) -> Result<Option<u64>, ShardError>
Scan the active shard for torn writes and truncate incomplete trailing lines.
A torn write leaves a partial line (no terminating \n) at the end
of the file. This method finds the last complete newline and
truncates everything after it.
Returns Ok(Some(bytes_truncated)) if a torn write was repaired,
or Ok(None) if the file was clean.
§Errors
Returns ShardError::Io if the shard file cannot be read or
truncated.
Sourcepub fn replay(&self) -> Result<String, ShardError>
pub fn replay(&self) -> Result<String, ShardError>
Read all event lines from all shards in chronological order.
Shards are read in lexicographic order (YYYY-MM sorts correctly).
Returns the concatenated content of all shard files.
§Errors
Returns ShardError::Io if any shard file cannot be read.
Sourcepub fn read_shard(&self, year: i32, month: u32) -> Result<String, ShardError>
pub fn read_shard(&self, year: i32, month: u32) -> Result<String, ShardError>
Read event lines from a specific shard.
§Errors
Returns ShardError::Io if the shard file cannot be read.
Sourcepub fn total_content_len(&self) -> Result<usize, ShardError>
pub fn total_content_len(&self) -> Result<usize, ShardError>
Compute the total concatenated byte size of all shards without reading their full contents.
This is used for advancing the projection cursor without paying the cost of a full replay.
§Errors
Returns ShardError::Io if any shard file metadata cannot be read.
Sourcepub fn replay_from_offset(
&self,
offset: usize,
) -> Result<(String, usize), ShardError>
pub fn replay_from_offset( &self, offset: usize, ) -> Result<(String, usize), ShardError>
Read shard content starting from the given absolute byte offset in the concatenated shard sequence.
Sealed shards that end entirely before offset are skipped without
reading their contents — only their file sizes are stat(2)’d.
Only content from offset onward is returned, bounding memory use to
new/unseen events rather than the full log.
Returns (new_content, total_len) where:
new_contentis the bytes fromoffsetto the end of all shards.total_lenis the total byte size of all shards concatenated (usable as the new cursor offset after processingnew_content).
§Errors
Returns ShardError::Io if shard metadata or file reads fail.
Sourcepub fn read_content_range(
&self,
start_offset: usize,
end_offset: usize,
) -> Result<String, ShardError>
pub fn read_content_range( &self, start_offset: usize, end_offset: usize, ) -> Result<String, ShardError>
Read bytes from the concatenated shard sequence in [start_offset, end_offset).
Only shards that overlap with the requested range are read. Shards entirely outside the range are stat(2)’d but not read.
This is used to read a small window around the projection cursor for hash validation without loading the full shard content.
§Errors
Returns ShardError::Io if any shard file cannot be read.
Sourcepub fn event_count(&self) -> Result<u64, ShardError>
pub fn event_count(&self) -> Result<u64, ShardError>
Count event lines across all shards (excluding comments and blanks).
§Errors
Returns ShardError::Io if any shard file cannot be read.
Sourcepub fn replay_lines(
&self,
) -> Result<impl Iterator<Item = Result<(usize, String)>>, ShardError>
pub fn replay_lines( &self, ) -> Result<impl Iterator<Item = Result<(usize, String)>>, ShardError>
Iterate over all event lines across all shards.
Yields (absolute_offset, line_content) pairs.
§Errors
Returns ShardError::Io if directory or shard reading fails.
Sourcepub fn replay_lines_from_offset(
&self,
offset: usize,
) -> Result<impl Iterator<Item = Result<(usize, String)>>, ShardError>
pub fn replay_lines_from_offset( &self, offset: usize, ) -> Result<impl Iterator<Item = Result<(usize, String)>>, ShardError>
Iterate over event lines starting from a given absolute byte offset.
Yields (absolute_offset, line_content) pairs.
§Errors
Returns ShardError::Io if directory or shard reading fails.
Sourcepub fn is_empty(&self) -> Result<bool, ShardError>
pub fn is_empty(&self) -> Result<bool, ShardError>
Check if the repository has any event shards.
§Errors
Returns ShardError::Io if the events directory cannot be read.