@homielab/jsonlet
v1.0.0
Published
The safest way to store small and medium-sized application state in a local JSON file.
Maintainers
Readme
Jsonlet
在本地 JSON 文件中存储中小型应用 state 的最安全方式。
English | Tiếng Việt | 简体中文
Jsonlet 是一个面向 Node.js 的轻量级 embedded local database,使用标准 JSON 文件提供安全、可预测且 type-safe 的 persistence。
开发者通常会直接使用 fs.readFile 和 fs.writeFile 将 CLI state、桌面应用设置或自动化数据存入 JSON 文件。这个看似简单的方案并不安全,经常会造成文件只写入一部分、concurrent write 导致数据损坏,以及 update 丢失。
Jsonlet 使用一套可预测的 API 封装 JSON persistence,提供 safe atomic writes、deterministic concurrency queues、cross-process file locking、schema migrations 和 crash recovery,同时保持零 runtime dependencies。
功能特性
- Safe Atomic Writes: 先写入临时文件并 flush,再以 atomic 方式替换主文件,避免 JSON 文件处于只写入一部分的状态。
- Concurrency & Locking: 通过 queue 串行处理同一 process 内的 mutation,并使用带 heartbeat 的 file lock 协调 cross-process write。
- Immutability by Default: cache 中读取的数据会被 deep-freeze。你无法绕过 transaction 意外修改 database snapshot。
- Schema Validation & Migrations: 可接入 Zod 或任意 validator,安全处理 version upgrade,并在 migration 前自动 backup。
- Self-Healing: 检测 stale lock,处理 Windows antivirus 导致的文件锁定(
EBUSY/EPERM),并自动从 backup 恢复损坏的文件。 - TypeScript Native: 提供完整的 type inference,且不使用任何
any。
安装
npm install @homielab/jsonlet
# 或者
pnpm add @homielab/jsonlet
# 或者
bun add @homielab/jsonlet需要 Node.js 18.0.0 或更高版本。
文档
- API Reference — constructor options、methods、hooks 和 errors。
- 示例 — 可直接运行的 production-style 使用示例。
- Changelog — release 历史和重要变更。
快速开始(TypeScript)
Jsonlet 保证 read 和 write 始终与你定义的 generic type 完全一致。
import { Jsonlet } from "@homielab/jsonlet";
interface MyDatabase {
counter: number;
users: Array<{ id: string; name: string }>;
}
const db = new Jsonlet<MyDatabase>("./data/db.json", {
defaults: () => ({
counter: 0,
users: [],
}),
});
// 1. 打开并加载 database
await db.open();
// 2. 同步读取 cache 中已 deep-freeze 的 snapshot
const state = db.read();
console.log(`当前用户数:${state.users.length}`);
// 3. 在 transaction queue 中安全更新 state
await db.update((draft) => {
draft.counter += 1;
draft.users.push({ id: crypto.randomUUID(), name: "Alice" });
});
await db.close();快速开始(JavaScript)
对于原生 JavaScript 用户,Jsonlet 提供完整的 IntelliSense 和 JSDoc 支持。
import { Jsonlet } from "@homielab/jsonlet";
const db = new Jsonlet("./data/db.json", {
defaults: {
theme: "dark",
notifications: true,
},
});
await db.open();
await db.update((draft) => {
draft.theme = "light";
});
console.log("当前主题:", db.read().theme);
await db.close();核心概念
Atomic Writes
调用 db.update() 时,Jsonlet 不会直接写入目标文件,而是执行以下步骤:
- 将完整的 serialized document 写入具有唯一名称的临时文件(
.db.json.jsonlet-tmp-<pid>)。 - 将临时文件 flush 到磁盘(使用
balanced或strictdurability mode 时)。 - 使用
fs.rename以 atomic 方式替换主文件;在 Windows 上遇到 antivirus 导致的临时EPERMlock 时,会使用 exponential backoff retry loop。 - 根据配置 sync parent directory。
Concurrency
- In-Process: 每次调用
update()或reload()都会进入串行 Promise queue。即使同时发起 100 次db.update(),它们也会逐个安全执行。 - Cross-Process: 如果两个独立的 Node.js script 操作同一个文件,Jsonlet 会通过 atomic directory lock、heartbeat,以及 write commit 前的 asynchronous token fencing,提供 best-effort cross-process coordination。
(注意:标准 JSON 文件没有 OS-level mandatory lock,因此在 process 长时间 suspend、event loop starvation 或 storage-level race condition 等极端情况下,理论上仍可能绕过 userspace lock。不过,Jsonlet 能够安全应对常见的 process 重叠、crash 后遗留的 stale lock 和慢速 disk I/O。)
高级用法
Schema Validation
Jsonlet 不强制使用特定的 validation library,但可以很好地集成 Zod 等工具:
import { Jsonlet } from "@homielab/jsonlet";
import { z } from "zod";
const Schema = z.object({
version: z.literal(1),
settings: z.object({ theme: z.string() }),
});
const db = new Jsonlet("./data/config.json", {
defaults: () => ({ version: 1, settings: { theme: "dark" } }),
parse: (input) => Schema.parse(input), // 加载时进行 validation!
});Migrations
Data shape 会随时间变化。Jsonlet 可以在启动时自动执行 migration:
const db = new Jsonlet("./data/db.json", {
defaults: () => ({ _v: 2, users: [] }),
schema: {
version: 2,
readVersion: (data) => data._v,
updateVersion: (data, version) => {
data._v = version;
return data;
},
migrations: {
1: (data) => {
// 从 v1 migrate 到 v2
data.users = data.users.map((u) => ({ ...u, status: "active" }));
return data;
},
},
},
});Backups and Recovery
如果用户用文本编辑器打开文件并误操作,文件可能会损坏。你可以配置 Jsonlet 自动 backup 数据,并在发生故障时执行 recovery。
const db = new Jsonlet("./data/db.json", {
defaults: () => ({ data: [] }),
backup: {
beforeMigration: true, // schema migration 前创建 backup
beforeEveryWrite: false, // 每次 write 前创建 backup
retention: { maxCount: 10 }, // 保留最近 10 个 backup
},
recovery: {
// 如果主文件包含损坏的 JSON,则自动恢复最新的有效 backup!
corruptedPrimary: "restore-latest-valid-backup",
quarantineBeforeReplace: true, // 将损坏文件保存为 .corrupt-xxx.json 以便检查
},
});使用限制(何时不应使用 Jsonlet)
Jsonlet 是 file persistence 工具,不是 database server。在 production 环境中使用前,请先了解以下限制:
- Memory Bound: 整个 document 都会加载到 memory 中,每次 commit 都会 serialize 并重写完整 document。对于 100 KB 文件,Jsonlet 速度非常快;对于 10 MB 文件,性能仍可接受;超过 50 MB 后,性能会明显下降。
- Throughput: Jsonlet 面向低到中等 write frequency。高吞吐量的 event logging 会受限于 disk I/O。
- Filesystem Edge Cases: File lock 和 atomic rename guarantee 依赖底层 OS 与 filesystem。Network drive(SMB/NFS)、云同步目录(Dropbox/OneDrive)及部分 Docker volume 配置可能削弱 atomic guarantee。
- No Queries: 不提供 query language、secondary index 或 relational join。你操作的是一个标准 JavaScript object。
如果应用需要 concurrent remote clients、multi-node clustering、relational constraints 或 partial document updates,请使用 SQLite、PostgreSQL 或 Redis 等标准 database。
支持的平台
- Node.js:
v18.0.0+ - OS: Linux、macOS、Windows
参与贡献
欢迎任何形式的贡献。请阅读贡献指南,了解如何设置 project 并提交 pull request。请按照 security policy 中的流程报告 vulnerability。
如需报告 bug 或提出 feature request,请使用 GitHub Issues。
许可证
MIT © 2026
