///|
/// FIST-Mbt store: 命名空间多库持久化管理器。
/// 按 ns 路由到独立 SQLite 文件 {data_dir}/{ns}.db,惰性打开(首次访问才建库)。
/// 每个 ns 拥有独立的物理库文件,实现多租户/多项目隔离;路径完全由参数传入,
/// 不做任何绝对路径硬编码。
///
/// 可变性模式与 AuditLog 一致:通过 self.stores.set / self.stores.remove 原地修改,
/// 无需在调用方重新赋值。这样可被模块级不可变绑定持有,供 MCP 工具闭包安全调用。
pub struct MultiStore {
stores : Map[String, SqliteStore]
data_dir : String
}
///|
/// 构造:仅记录根目录,不打开任何库(惰性)。
pub fn MultiStore::new(data_dir? : String = ".") -> MultiStore {
{ stores: Map([]), data_dir, }
}
///|
/// 打开(或复用已打开的)某个命名空间对应的库文件 {data_dir}/{ns}.db。
/// 失败返回 false,成功返回 true。
pub fn MultiStore::open(
self : MultiStore,
ns~ : String,
data_dir~ : String,
) -> Bool {
if ns == "" {
return false
}
// 已打开则直接复用(幂等)
if self.stores.contains(ns) {
return true
}
let path = "\{data_dir}/\{ns}.db"
match SqliteStore::open(path) {
None => false
Some(s) => {
self.stores.set(ns, s)
true
}
}
}
///|
/// 获取某 ns 的存储;惰性打开:尚未打开时按当前 data_dir 首次创建/打开。
/// 打开失败返回 None。
pub fn MultiStore::get(self : MultiStore, ns : String) -> SqliteStore? {
match self.stores.get(ns) {
Some(s) => Some(s)
None => {
// get 不修改 self,直接尝试打开物理文件(惰性)
let path = "\{self.data_dir}/\{ns}.db"
match SqliteStore::open(path) {
Some(s) => Some(s)
None => None
}
}
}
}
///|
/// 关闭并移除某个命名空间(不删除物理文件,可重新 open 恢复)。
pub fn MultiStore::close(self : MultiStore, ns : String) -> Unit {
if self.stores.contains(ns) {
self.stores.remove(ns)
}
}
///|
/// 关闭全部命名空间。
pub fn MultiStore::close_all(self : MultiStore) -> Unit {
let keys = self.ns_list()
for k in keys {
self.stores.remove(k)
}
}
///|
/// 列出已打开的命名空间(Map 遍历顺序,不保证稳定)。
pub fn MultiStore::ns_list(self : MultiStore) -> Array[String] {
let out : Array[String] = []
self.stores.each(fn(k, _) { out.push(k) })
out
}
///|
/// 每个已打开 ns 的任务数统计(直接用该 ns 库的 tasks 表 COUNT)。
/// 返回 Json 数组,元素形如 {"ns": "...", "total": N}。
pub fn MultiStore::ns_stats(self : MultiStore) -> Json {
let m : Map[String, Json] = Map([])
fn count_tasks(store : SqliteStore) -> Int {
let mut n = 0
match store.db {
None => n = 0
Some(db) =>
match db.prepare("SELECT COUNT(*) FROM tasks") {
None => n = 0
Some(stmt) => {
if stmt.step() {
n = stmt.column_int(0)
}
stmt.finalize()
}
}
}
n
}
self.stores.each(fn(ns, store) {
let item : Map[String, Json] = Map([])
item.set("ns", Json::string(ns))
item.set("total", Json::number(count_tasks(store).to_double()))
ignore(m.set(ns, Json::object(item)))
})
Json::object(m)
}