use std::io::Write;
use std::path::Path;
use crate::error::MemoryError;
use crate::storage::{is_internal_scaffolding, strip_reserved_keys};
const EXPORT_BATCH: usize = 512;
pub fn export_jsonl<W: Write>(
store_dir: &Path,
out: &mut W,
include_internal: bool,
) -> Result<u64, MemoryError> {
if !store_dir.is_dir() {
return Err(MemoryError::Storage(velesdb_core::Error::Query(format!(
"no store directory at {} — nothing to export",
store_dir.display()
))));
}
let db = velesdb_core::Database::open(store_dir)?;
let mut written = 0_u64;
let mut cursor: Option<u64> = None;
loop {
let (facts, next) =
crate::migration::scroll_page(&db, "_semantic_memory", cursor, EXPORT_BATCH)?;
written += write_page(out, &facts, include_internal)?;
match next {
Some(id) => cursor = Some(id),
None => break,
}
}
Ok(written)
}
fn write_page<W: Write>(
out: &mut W,
facts: &[crate::migration::RawFact],
include_internal: bool,
) -> Result<u64, MemoryError> {
let mut written = 0_u64;
for fact in facts {
if let Some(line) = jsonl_line(fact, include_internal) {
writeln!(out, "{line}").map_err(|err| {
MemoryError::Storage(velesdb_core::Error::Query(format!(
"export write failed: {err}"
)))
})?;
written += 1;
}
}
Ok(written)
}
fn jsonl_line(
fact: &crate::migration::RawFact,
include_internal: bool,
) -> Option<serde_json::Value> {
let split = crate::storage::RawListedFact::from_raw(fact);
if !include_internal && is_internal_scaffolding(&split.payload) {
return None;
}
let metadata = if include_internal {
(!split.payload.is_empty()).then_some(split.payload)
} else {
strip_reserved_keys(Some(split.payload))
};
Some(serde_json::json!({
"id": split.id,
"id_str": split.id.to_string(),
"content": split.content,
"metadata": metadata,
}))
}
#[cfg(test)]
#[path = "export_tests.rs"]
mod tests;