Skip to main content

ferridriver_script/
vars.rs

1//! Session-level variable store exposed to scripts as the `vars` global.
2//!
3//! The engine takes a `VarsStore` trait object so callers (the MCP server)
4//! can plug in their own storage. A simple in-memory implementation is
5//! provided for tests and stand-alone use.
6
7use std::sync::RwLock;
8
9use rustc_hash::FxHashMap;
10
11/// Session-level key/value store.
12///
13/// Called from JS via the `vars` global: `vars.get(name)`, `vars.set(name, value)`,
14/// `vars.has(name)`, `vars.delete(name)`, `vars.keys()`.
15pub trait VarsStore: Send + Sync {
16  fn get(&self, name: &str) -> Option<String>;
17  fn set(&self, name: &str, value: String);
18  fn has(&self, name: &str) -> bool;
19  fn delete(&self, name: &str);
20  fn keys(&self) -> Vec<String>;
21}
22
23/// Simple in-memory `VarsStore` backed by an `RwLock<FxHashMap>`.
24///
25/// Cheap to construct and safe to share across script runs; the MCP server
26/// holds one of these per session so `vars` survives across `run_script` calls.
27#[derive(Default)]
28pub struct InMemoryVars {
29  inner: RwLock<FxHashMap<String, String>>,
30}
31
32impl InMemoryVars {
33  #[must_use]
34  pub fn new() -> Self {
35    Self::default()
36  }
37}
38
39impl VarsStore for InMemoryVars {
40  fn get(&self, name: &str) -> Option<String> {
41    self.inner.read().ok().and_then(|guard| guard.get(name).cloned())
42  }
43
44  fn set(&self, name: &str, value: String) {
45    if let Ok(mut guard) = self.inner.write() {
46      guard.insert(name.to_string(), value);
47    }
48  }
49
50  fn has(&self, name: &str) -> bool {
51    self.inner.read().ok().is_some_and(|guard| guard.contains_key(name))
52  }
53
54  fn delete(&self, name: &str) {
55    if let Ok(mut guard) = self.inner.write() {
56      guard.remove(name);
57    }
58  }
59
60  fn keys(&self) -> Vec<String> {
61    self
62      .inner
63      .read()
64      .map(|guard| guard.keys().cloned().collect())
65      .unwrap_or_default()
66  }
67}