pub trait DataStore: Send + Sync {
// Required methods
fn create_data_link(
&self,
pack: &str,
handler: &str,
source_file: &Path,
) -> Result<PathBuf>;
fn create_user_link(
&self,
datastore_path: &Path,
user_path: &Path,
) -> Result<()>;
fn run_and_record(
&self,
pack: &str,
handler: &str,
executable: &str,
arguments: &[String],
sentinel: &str,
force: bool,
) -> Result<()>;
fn has_sentinel(
&self,
pack: &str,
handler: &str,
sentinel: &str,
) -> Result<bool>;
fn remove_state(&self, pack: &str, handler: &str) -> Result<()>;
fn has_handler_state(&self, pack: &str, handler: &str) -> Result<bool>;
fn list_pack_handlers(&self, pack: &str) -> Result<Vec<String>>;
fn list_handler_sentinels(
&self,
pack: &str,
handler: &str,
) -> Result<Vec<String>>;
fn write_rendered_file(
&self,
pack: &str,
handler: &str,
filename: &str,
content: &[u8],
) -> Result<PathBuf>;
fn write_rendered_dir(
&self,
pack: &str,
handler: &str,
relative: &str,
) -> Result<PathBuf>;
fn sentinel_path(
&self,
pack: &str,
handler: &str,
sentinel: &str,
) -> PathBuf;
}Expand description
Dodot’s storage interface.
State is represented entirely by symlinks and sentinel files in the filesystem — no database, no lock files. The 8 methods break into three groups:
Mutations — modify state:
Queries — read state:
Required Methods§
Sourcefn create_data_link(
&self,
pack: &str,
handler: &str,
source_file: &Path,
) -> Result<PathBuf>
fn create_data_link( &self, pack: &str, handler: &str, source_file: &Path, ) -> Result<PathBuf>
Creates an intermediate symlink in the datastore:
handler_data_dir(pack, handler) / filename -> source_file
Returns the absolute path of the created datastore link. Idempotent: if the link exists and already points to the correct source, this is a no-op.
Sourcefn create_user_link(
&self,
datastore_path: &Path,
user_path: &Path,
) -> Result<()>
fn create_user_link( &self, datastore_path: &Path, user_path: &Path, ) -> Result<()>
Creates a user-visible symlink:
user_path -> datastore_path
This is the second leg of the double-link architecture. Creates parent directories as needed.
Sourcefn run_and_record(
&self,
pack: &str,
handler: &str,
executable: &str,
arguments: &[String],
sentinel: &str,
force: bool,
) -> Result<()>
fn run_and_record( &self, pack: &str, handler: &str, executable: &str, arguments: &[String], sentinel: &str, force: bool, ) -> Result<()>
Executes command via shell and records a sentinel on success.
Idempotent: if the sentinel already exists, the command is not
re-run. The sentinel file stores completed|{timestamp}.
Edge case: if the command succeeds but the sentinel write fails, a subsequent call will re-run the command. This is by design — re-running is safer than falsely marking as complete. Install scripts should be idempotent to handle this.
Sourcefn has_sentinel(
&self,
pack: &str,
handler: &str,
sentinel: &str,
) -> Result<bool>
fn has_sentinel( &self, pack: &str, handler: &str, sentinel: &str, ) -> Result<bool>
Checks whether a sentinel exists for this pack/handler.
Sourcefn remove_state(&self, pack: &str, handler: &str) -> Result<()>
fn remove_state(&self, pack: &str, handler: &str) -> Result<()>
Removes all state for a pack/handler pair.
Deletes the handler data directory and everything in it.
Sourcefn has_handler_state(&self, pack: &str, handler: &str) -> Result<bool>
fn has_handler_state(&self, pack: &str, handler: &str) -> Result<bool>
Checks if any state exists for a pack/handler pair.
Sourcefn list_pack_handlers(&self, pack: &str) -> Result<Vec<String>>
fn list_pack_handlers(&self, pack: &str) -> Result<Vec<String>>
Lists handler names that have state for a pack.
Sourcefn list_handler_sentinels(
&self,
pack: &str,
handler: &str,
) -> Result<Vec<String>>
fn list_handler_sentinels( &self, pack: &str, handler: &str, ) -> Result<Vec<String>>
Lists sentinel file names for a pack/handler.
Sourcefn write_rendered_file(
&self,
pack: &str,
handler: &str,
filename: &str,
content: &[u8],
) -> Result<PathBuf>
fn write_rendered_file( &self, pack: &str, handler: &str, filename: &str, content: &[u8], ) -> Result<PathBuf>
Writes a regular file (not a symlink) into the datastore.
Used for preprocessor-expanded files where the datastore holds rendered content rather than a symlink to the source. Returns the absolute path of the written file. Idempotent: overwrites if the file already exists.
filename must be a safe relative path — no absolute paths, no
.. components. Callers (typically the preprocessing pipeline)
are expected to validate before calling. Implementations should
also reject unsafe paths as defense-in-depth.
Sourcefn write_rendered_dir(
&self,
pack: &str,
handler: &str,
relative: &str,
) -> Result<PathBuf>
fn write_rendered_dir( &self, pack: &str, handler: &str, relative: &str, ) -> Result<PathBuf>
Creates a directory (mkdir -p) inside the datastore and returns its absolute path. Used for preprocessor-expanded directory entries (e.g. directory markers from tar archives).
Same path-safety constraints as [write_rendered_file].