1use crate::compiler::Program;
16use crate::host::{FuncDef, TryDef};
17use fusevm::Chunk;
18use rkyv::{Archive, Deserialize as RkyvDe, Serialize as RkyvSer};
19use serde::{Deserialize, Serialize};
20use std::hash::{Hash, Hasher};
21use std::path::PathBuf;
22
23const SCHEMA: u64 = 2;
30
31#[derive(Archive, RkyvSer, RkyvDe, Default)]
33#[archive(check_bytes)]
34struct Shard {
35 entries: Vec<Entry>,
36}
37
38#[derive(Archive, RkyvSer, RkyvDe)]
39#[archive(check_bytes)]
40struct Entry {
41 key: u64,
42 verify: u64,
47 blob: Vec<u8>,
48}
49
50#[derive(Serialize, Deserialize)]
52struct CProg {
53 main: Chunk,
54 functions: Vec<(String, FuncDef)>,
55 tries: Vec<TryDef>,
56}
57
58pub fn key_for(src: &str) -> u64 {
60 let mut h = rustc_hash::FxHasher::default();
61 SCHEMA.hash(&mut h);
62 src.hash(&mut h);
63 h.finish()
64}
65
66fn verify_for(src: &str) -> u64 {
69 use std::collections::hash_map::DefaultHasher;
70 let mut h = DefaultHasher::new();
71 SCHEMA.hash(&mut h);
72 src.len().hash(&mut h);
73 src.hash(&mut h);
74 h.finish()
75}
76
77fn shard_path() -> Option<PathBuf> {
78 let dir = dirs::home_dir()?.join(".node-js");
79 let _ = std::fs::create_dir_all(&dir);
80 Some(dir.join("scripts.rkyv"))
81}
82
83fn load_shard() -> Shard {
84 let Some(path) = shard_path() else {
85 return Shard::default();
86 };
87 let Ok(bytes) = std::fs::read(&path) else {
88 return Shard::default();
89 };
90 rkyv::from_bytes::<Shard>(&bytes).unwrap_or_default()
91}
92
93fn write_shard(shard: &Shard) -> Result<(), String> {
94 let path = shard_path().ok_or("no home dir for cache")?;
95 let bytes = rkyv::to_bytes::<_, 4096>(shard).map_err(|e| format!("cache serialize: {e}"))?;
96 static SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
103 let n = SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
104 let tmp = path.with_extension(format!("rkyv.tmp.{}.{n}", std::process::id()));
105 std::fs::write(&tmp, &bytes).map_err(|e| format!("cache write: {e}"))?;
106 std::fs::rename(&tmp, &path).map_err(|e| {
107 let _ = std::fs::remove_file(&tmp);
108 format!("cache rename: {e}")
109 })
110}
111
112pub fn load(src: &str) -> Option<Program> {
114 let key = key_for(src);
115 let verify = verify_for(src);
116 let shard = load_shard();
117 let entry = shard
118 .entries
119 .iter()
120 .find(|e| e.key == key && e.verify == verify)?;
121 let cp: CProg = bincode::deserialize(&entry.blob).ok()?;
122 Some(Program {
123 main: cp.main,
124 functions: cp.functions,
125 tries: cp.tries,
126 })
127}
128
129pub fn store(src: &str, prog: &Program) -> Result<(), String> {
131 let key = key_for(src);
132 let verify = verify_for(src);
133 let cp = CProg {
134 main: prog.main.clone(),
135 functions: prog.functions.clone(),
136 tries: prog.tries.clone(),
137 };
138 let blob = bincode::serialize(&cp).map_err(|e| format!("cache encode: {e}"))?;
139 let mut shard = load_shard();
140 shard.entries.retain(|e| e.key != key);
141 shard.entries.push(Entry { key, verify, blob });
142 write_shard(&shard)
143}