kevy4.0

装进产品里

把存储
放进东西本身

没有服务端,没有 socket,没有网络。这个引擎可以是一个你直接调用的 struct,可以是一个 151 KB 的 WebAssembly 模块,也可以是一颗没有操作系统的芯片上的 no_std 库——而且这三种情况下,它是同一个引擎、同一批命令。

为什么合适

每一个必须离线可用的应用,最后都会自己写一层存储。桌面应用会得到 SQLite,外加一套没人想要的 schema。web 应用会得到 localStorage,然后撞上 5 MB 的上限,再发现它是同步的、而且只能存字符串,于是又得到 IndexedDB 和包在它外面的一层抽象。设备会得到一个手写在 flash 里的环形缓冲区。

这三件事是同一个问题,也可以是同一个解。kevy 嵌进去不带任何进程边界,发到浏览器时带着真的 TTL 和真的发布订阅,也能在一颗 Cortex-M 上用一块固定 arena、完全不用分配器地启动。最后这一条,CI 每次 push 都会验一遍。

怎么做——在一个 Rust 程序里

没有 socket,没有序列化,没有第二个进程。数据是持久的,打开时会重放自己的日志。
kevy-embedded = "4.0"

let db = Db::open("data/")?;
db.set(b"session:7f3a", b"{\"user\":\"ada\"}", Some(Duration::from_secs(3600)))?;
assert_eq!(db.get(b"session:7f3a")?.is_some(), true);

// need other processes to reach it later? open the RESP listener
// and your redis-cli works, without changing any of the above.
db.listen("127.0.0.1:6379")?;

怎么做——在浏览器标签页里

gzip 之后 151 KB。落在浏览器自己的文件系统上,刷新之后还在,发布订阅还能跨标签页。
import { open } from "@goliajp/kevy";

const db = await open({ persist: { name: "app" } });

db.set("cart:u881", JSON.stringify(items), { ttlMs: 86_400_000 });
db.get("cart:u881");        // still there after a reload
db.pttl("cart:u881");       // the engine expires it, not your code

db.subscribe("sync", (payload) => merge(payload));   // other tabs

怎么做——在单片机上

no_std,没有分配器,没有操作系统。一块由你自己定大小的固定 arena。
# Cargo.toml
kevy-store = { version = "4.0", default-features = false }

# no_std, no heap: the store lives in an arena you provide
let mut arena = [0u8; 64 * 1024];
let mut store = Store::new_in(&mut arena);
store.set(b"temp", b"21.4")?;
代价是什么

在浏览器里,做一次小的同步读,localStorage 更快——它就是页面自己地址空间里的一个 map,任何建在 OPFS 上的东西都赢不了它这一点。kevy 赢在别的地方,而那些地方本来就是 localStorage 不该被用的理由:真的 TTL、没有 5 MB 上限、value 是字节而不是字符串、写入不挡主线程。在单片机上,arena 的大小由你自己定,运行时不能再涨——这就是没有分配器的含义。还有,嵌入式的存储不是共享的:如果第二个进程也要用这份数据,你要的是服务端,或者嵌入式的 RESP listener。

接下来