npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@homielab/jsonlet

v1.0.0

Published

The safest way to store small and medium-sized application state in a local JSON file.

Readme

Jsonlet

在本地 JSON 文件中存储中小型应用 state 的最安全方式。

npm version CI Node.js License: MIT

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 不会直接写入目标文件,而是执行以下步骤:

  1. 将完整的 serialized document 写入具有唯一名称的临时文件(.db.json.jsonlet-tmp-<pid>)。
  2. 将临时文件 flush 到磁盘(使用 balanced 或 strict durability mode 时)。
  3. 使用 fs.rename 以 atomic 方式替换主文件;在 Windows 上遇到 antivirus 导致的临时 EPERM lock 时,会使用 exponential backoff retry loop。
  4. 根据配置 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 环境中使用前,请先了解以下限制:

  1. Memory Bound: 整个 document 都会加载到 memory 中,每次 commit 都会 serialize 并重写完整 document。对于 100 KB 文件,Jsonlet 速度非常快;对于 10 MB 文件,性能仍可接受;超过 50 MB 后,性能会明显下降。
  2. Throughput: Jsonlet 面向低到中等 write frequency。高吞吐量的 event logging 会受限于 disk I/O。
  3. Filesystem Edge Cases: File lock 和 atomic rename guarantee 依赖底层 OS 与 filesystem。Network drive(SMB/NFS)、云同步目录(Dropbox/OneDrive)及部分 Docker volume 配置可能削弱 atomic guarantee。
  4. 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