use crate::compiler::Program;
use crate::host::{FuncDef, TryDef};
use fusevm::Chunk;
use rkyv::{Archive, Deserialize as RkyvDe, Serialize as RkyvSer};
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
const SCHEMA: u64 = 2;
#[derive(Archive, RkyvSer, RkyvDe, Default)]
#[archive(check_bytes)]
struct Shard {
entries: Vec<Entry>,
}
#[derive(Archive, RkyvSer, RkyvDe)]
#[archive(check_bytes)]
struct Entry {
key: u64,
verify: u64,
blob: Vec<u8>,
}
#[derive(Serialize, Deserialize)]
struct CProg {
main: Chunk,
functions: Vec<(String, FuncDef)>,
tries: Vec<TryDef>,
}
pub fn key_for(src: &str) -> u64 {
let mut h = rustc_hash::FxHasher::default();
SCHEMA.hash(&mut h);
src.hash(&mut h);
h.finish()
}
fn verify_for(src: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut h = DefaultHasher::new();
SCHEMA.hash(&mut h);
src.len().hash(&mut h);
src.hash(&mut h);
h.finish()
}
fn shard_path() -> Option<PathBuf> {
let dir = dirs::home_dir()?.join(".node-js");
let _ = std::fs::create_dir_all(&dir);
Some(dir.join("scripts.rkyv"))
}
fn load_shard() -> Shard {
let Some(path) = shard_path() else {
return Shard::default();
};
let Ok(bytes) = std::fs::read(&path) else {
return Shard::default();
};
rkyv::from_bytes::<Shard>(&bytes).unwrap_or_default()
}
fn write_shard(shard: &Shard) -> Result<(), String> {
let path = shard_path().ok_or("no home dir for cache")?;
let bytes = rkyv::to_bytes::<_, 4096>(shard).map_err(|e| format!("cache serialize: {e}"))?;
static SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let n = SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let tmp = path.with_extension(format!("rkyv.tmp.{}.{n}", std::process::id()));
std::fs::write(&tmp, &bytes).map_err(|e| format!("cache write: {e}"))?;
std::fs::rename(&tmp, &path).map_err(|e| {
let _ = std::fs::remove_file(&tmp);
format!("cache rename: {e}")
})
}
pub fn load(src: &str) -> Option<Program> {
let key = key_for(src);
let verify = verify_for(src);
let shard = load_shard();
let entry = shard
.entries
.iter()
.find(|e| e.key == key && e.verify == verify)?;
let cp: CProg = bincode::deserialize(&entry.blob).ok()?;
Some(Program {
main: cp.main,
functions: cp.functions,
tries: cp.tries,
})
}
pub fn store(src: &str, prog: &Program) -> Result<(), String> {
let key = key_for(src);
let verify = verify_for(src);
let cp = CProg {
main: prog.main.clone(),
functions: prog.functions.clone(),
tries: prog.tries.clone(),
};
let blob = bincode::serialize(&cp).map_err(|e| format!("cache encode: {e}"))?;
let mut shard = load_shard();
shard.entries.retain(|e| e.key != key);
shard.entries.push(Entry { key, verify, blob });
write_shard(&shard)
}