# @backloghq/opslog 中文文档

规范地址：https://npmcn.com/package/%40backloghq/opslog/

版本：0\.8\.1
npm 元数据更新时间：2026-04-11T17:54:37.905Z
中文译文更新时间：2026-11-07T23:22:39.890Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40backloghq/opslog
原文对照：https://npmcn.com/package/%40backloghq/opslog/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：89.3 kB，27 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：0 个
- 首次发布：2026-04-06T20:21:32.299Z
- 最新版本发布：2026-04-11T17:54:37.765Z
- 近 30 天下载量：2059（2026-10-08 至 2026-11-06，npm 官方下载统计）
- 已发布版本数：16（近 12 个月 16 个）

## 中文简介

嵌入事件源文档存储. 带有不可改变快照, 0 个本地依赖的附加操作日志 .

## 安装

```sh
npm install @backloghq/opslog
```

也可以使用：`pnpm add @backloghq/opslog`、`yarn add @backloghq/opslog`、`bun add @backloghq/opslog`

## README

# 行动日志

为Node.js所嵌入的事件源文档存储. 零原属地.

每一次突变都被记录为一个仅附加日志中的操作. 当前状态是通过从最新的快照中重放操作而得到的. 你得到崩溃安全,撤消,审计线索, 和同步准备,没有数据库服务器.

## 安装

```bash
npm install @backloghq/opslog
```

## 使用量

```typescript
import { Store } from "@backloghq/opslog";

const store = new Store<{ name: string; status: string }>();
await store.open("./data");

// Create
await store.set("task-1", { name: "Build API", status: "active" });

// Read
const task = store.get("task-1");

// Update
await store.set("task-1", { ...task, status: "done" });

// Delete
await store.delete("task-1");

// Undo the delete
await store.undo();

// Query
const active = store.filter((r) => r.status === "active");
const count = store.count((r) => r.status === "done");

// Batch (single disk write for multiple operations)
await store.batch(() => {
  store.set("a", { name: "A", status: "active" });
  store.set("b", { name: "B", status: "active" });
  store.set("c", { name: "C", status: "active" });
});

// Close (checkpoints automatically)
await store.close();
```

国家幸存下来的重启——重新打开同一个目录和所有东西.

## 如何运作

```
data/
  manifest.json                    # Points to current snapshot + ops file(s)
  snapshots/
    snap-<timestamp>.jsonl          # Immutable full-state capture (JSONL: header + one line per record)
  ops/
    ops-<timestamp>.jsonl          # Append-only operation log (single-writer)
    agent-<id>-<timestamp>.jsonl   # Per-agent operation log (multi-writer)
  archive/
    archive-<period>.json          # Old records, lazy-loaded
```

**写作** 将操作( 1 JSON 线) 附加到操作文件 . **读取** 来源于由最新快照 + ops 重放所构建的内存地图. **检查站** 实现当前状态为新的不可改变的快照.

每个操作记录上一个值,所以 **撤销** 弹出上一个操作并恢复旧状态. 操作日志作为 **审计线索** 和自然单位 **同步** 在节点之间.

## API 密码

### 生命周期

```typescript
await store.open(dir, options?)   // Load state from directory
await store.close()               // Checkpoint and close
```

### 拥挤

```typescript
store.get(id)                     // Get record by ID
await store.set(id, value)        // Create or update
await store.delete(id)            // Remove (throws if not found)
store.has(id)                     // Check existence
```

### 查询

```typescript
store.all()                       // All records
store.entries()                   // All [id, record] pairs
store.filter(predicate)           // Records matching predicate
store.count(predicate?)           // Count (all or matching)
store.getManifest()               // Read-only ManifestInfo (snapshot/WAL paths, stats)
```

### (供外部消费者使用)

```typescript
// Stream snapshot records without loading all into memory
for await (const [id, record] of store.streamSnapshot()) {
  process(id, record);
}

// Read WAL operations (optionally since a timestamp)
for await (const op of store.getWalOps(sinceTimestamp?)) {
  // op: { ts, op: "set"|"delete", id, data?, prev }
}
```

### 批次

```typescript
await store.batch(() => {         // Multiple ops, single disk write
  store.set("a", valueA);        // Rolls back all on error
  store.set("b", valueB);
});
```

空批次( 无) `set`编号 :`delete` ——没有执行I/O.

### 历史

```typescript
await store.undo()                // Undo last operation
store.getHistory(id)              // All operations for a record
store.getOps(since?)              // Operations since timestamp
```

### 维修

```typescript
await store.compact()             // Create new snapshot, clear ops
await store.archive(predicate)    // Move matching records to archive
await store.loadArchive(segment)  // Lazy-load archived records
store.listArchiveSegments()       // List available archive files
store.stats()                     // { activeRecords, opsCount, archiveSegments }
await store.refresh()             // Reload from all agent WALs (multi-writer only)
```

### Blob 存储( 通过存储后端)

```typescript
await backend.writeBlob("data/file.jsonl", buffer);  // Write at relative path
const buf = await backend.readBlob("data/file.jsonl"); // Read full file
const range = await backend.readBlobRange("data/file.jsonl", 1024, 256); // Byte-range read
const names = await backend.listBlobs("data");         // List files under prefix
await backend.deleteBlob("data/file.jsonl");            // Delete file
```

`readBlobRange` 可以在 JSONL 唱片商店中实现 O(1) 点查—— 寻求字节相抵, 读取精确长度 。 FsBackend 用途 `fs.read` 页:1 S3Backend使用HTTP Range头.

## 选项

```typescript
await store.open(dir, {
  checkpointThreshold: 100,       // Auto-checkpoint after N ops (default: 100)
  checkpointOnClose: true,        // Checkpoint when close() is called (default: true)
  version: 1,                     // Schema version
  migrate: (record, fromVersion) => record, // Migration function
  readOnly: false,                // Open in read-only mode (default: false)
  skipLoad: false,                // Skip loading snapshot/WAL into memory (default: false)
  writeMode: "immediate",         // "immediate" (default), "group" (~12x faster), or "async" (~50x faster, lossy on crash)
  groupCommitSize: 50,            // Group: flush after N ops (default: 50)
  groupCommitMs: 100,             // Group: flush after N ms (default: 100)
  agentId: "agent-A",             // Enable multi-writer mode (optional)
  backend: new FsBackend(),       // Custom storage backend (optional, default: FsBackend)
});
```

## 组提交

缓冲写入内存并冲出为单盘写出. 对持续写作来说.

```typescript
const store = new Store();
await store.open("./data", {
  writeMode: "group",     // Buffer ops, flush periodically
  groupCommitSize: 50,    // Flush when buffer has 50 ops
  groupCommitMs: 100,     // Or after 100ms idle
});

// Writes are buffered — no fsync per op
await store.set("a", valueA);
await store.set("b", valueB);  // Both flushed together

// Explicit flush if needed
await store.flush();

// close() always flushes before shutting down
await store.close();
```

### 同步模式

对于最大写入吞吐量, 使用 `writeMode: "async"`。在缓冲内存后立即写入解析——热路没有磁盘 I/O. ~50x比即时模式快.

```typescript
const store = new Store();
await store.open("./data", {
  writeMode: "async",
  groupCommitSize: 50,
  groupCommitMs: 100,
});

await store.set("a", valueA);  // Returns instantly — buffered in memory
await store.set("b", valueB);  // Same

// Ensure durability before exit
await store.sync();
await store.close();
```

**崩溃语义 :** 数据缓冲自上次冲出是 **丢失** 不干净的关闭。 打电话 `sync()` 在进程退出后可持久. `close()` 总是冲来冲去.

**安全性:** 强迫 `"immediate"` 何时 `agentId` 设置(多写器模式)。 其他特工无法看到缓冲行动,因此团体/同步承诺只是单写.

**何时使用哪种模式 :**

-   `"immediate"` (默认)——每作都耐用. 当数据丢失是不可接受的时使用 .
-   `"group"` - 写作是批量的,但呼叫者仍在等待冲浪。 ~12x快一点. 崩溃损失最多 `groupCommitMs` 数据数.
-   `"async"` - 马上回信 ~50x快点. 崩溃失去所有未流出的数据 。 最适合高通量、耐久性、耐撞性的工作.

## 多写器模式

多个特工可以同时给同一家商店写信. 每个代理都得到自己的WAL文件——没有写争议.

```typescript
// Agent A (process 1 / machine 1)
const storeA = new Store<Task>();
await storeA.open("./data", { agentId: "agent-A" });
await storeA.set("task-1", { title: "Build API", status: "active" });
await storeA.close();

// Agent B (process 2 / machine 2)
const storeB = new Store<Task>();
await storeB.open("./data", { agentId: "agent-B" });
// B sees A's writes on open
storeB.get("task-1"); // { title: "Build API", status: "active" }
await storeB.set("task-2", { title: "Write tests", status: "active" });
await storeB.close();
```

### 如何运作

-   每个代理都给 `ops/agent-{id}-{timestamp}.jsonl` ——单独文件,写入不需要锁定
-   业务携带a [兰波特钟](https://en.wikipedia.org/wiki/Lamport_timestamp) 用于订购
-   打开 `open()`,所有代理 WAL 文件由 `(clock, agentId)` 确定总顺序
-   冲突(两个代理写同样的密钥)通过 **最后写手胜出** 按时钟值
-   `undo()` 只取消调用代理的最后一次操作
-   `compact()` 获取压缩锁, 抓取合并状态, 并重置所有 WAL 文件
-   `refresh()` 重新阅读所有代理 WALs 来取取其他代理的写作

### 解决冲突

当两个特工修改了同一个密钥时,使用更高一等的Lamport时钟的操作获胜. 如果时钟相等,则词典上更高的代理ID获胜. 这是决定性的——所有特工都到达同一状态,无论重播命令如何.

```typescript
// Agent A sets "shared" (clock=1)
await storeA.set("shared", { value: "from-A" });

// Agent B opens (sees clock=1), sets "shared" (clock=2)
await storeB.set("shared", { value: "from-B" });

// B wins — higher clock
store.get("shared"); // { value: "from-B" }
```

## 自定义存储后端

opslog 使用可插件 `StorageBackend` 用于所有 I/O 的接口。 默认是 `FsBackend` (本地文件系统). 您可以为 S3 、 数据库或其他存储系统执行自己的后端.

```typescript
import { Store, FsBackend } from "@backloghq/opslog";
import type { StorageBackend } from "@backloghq/opslog";

// Use the default filesystem backend (implicit)
const store = new Store();
await store.open("./data");

// Or pass a custom backend explicitly
const store = new Store();
await store.open("./data", { backend: new FsBackend() });

// Or implement your own
class S3Backend implements StorageBackend {
  // ... implement all methods
}
const store = new Store();
await store.open("s3://bucket/prefix", { backend: new S3Backend() });
```

## 仅读模式

在不获取写锁的情况下打开商店阅读. 可用于仪表板,备份进程,或多个阅读器与单个写作机并列.

```typescript
const reader = new Store();
await reader.open("./data", { readOnly: true });

// All reads work
const tasks = reader.all();
const active = reader.filter((t) => t.status === "active");

// All mutations throw
await reader.set("x", value); // Error: Store is read-only
```

只读存储在打开时加载最新快照并重放操作. 他们不关闭检查站。 多个只读商店可以同时打开同一个目录和一个写手同时打开.

## 货币

所有州级业务(`set`, (中文(简体) ), `delete`, (中文(简体) ), `batch`, (中文(简体) ), `undo`, (中文(简体) ), `compact`, (中文(简体) ), `archive`)通过内部活性突变被序列化. 这样可以防止同时发生的突变——例如, `compact()` 交换操作文件时 `set()` 正在附加,或 `undo()` 切换时 `set()` 正在写作.

读取操作( E)`get`, (中文(简体) ), `all`, (中文(简体) ), `filter`, (中文(简体) ), `count`, (中文(简体) ), `has`, (中文(简体) ), `entries`)是同步和无锁的.

咨询目录写入锁( N)`.lock` 有 PID 的文件)防止两个进程打开同一商店。 从已坠毁的流程中自动取出 Stale 锁.

## 崩溃安全

-   **操作文件**: 只写附件 。 坠机中继器最多输掉最后一次操作. 恢复时跳过错误的行 .
-   **抓图**:不可改变. 写入临时文件,然后在解剖学上重新命名.
-   **声明**:通过临时文件重命名进行解剖替换. 总是指向有效的快照 .
-   **撤销**: 使用 `ftruncate()` 单原子 POSIX syscall. O(1) 无论文件大小.

没有崩溃的数据腐败 。 最多一次飞行行动失败.

## Schema 迁移

```typescript
const store = new Store();
await store.open(dir, {
  version: 2,
  migrate: (record, fromVersion) => {
    if (fromVersion < 2) return { ...record, newField: "default" };
    return record;
  },
});
```

记录在打开时移入内存。 下一个检查站继续维持迁移状态.

## 发展

```bash
npm run build          # Compile TypeScript
npm run lint           # ESLint
npm test               # Run tests
npm run test:coverage  # Tests with coverage
```

## 许可证

麻省理工学院

## 常见问题

### @backloghq/opslog 是什么？

嵌入事件源文档存储\. 带有不可改变快照, 0 个本地依赖的附加操作日志 \.

### 如何安装 @backloghq/opslog？

在项目目录运行 npm install @backloghq/opslog，或使用 pnpm add / yarn add / bun add。

### @backloghq/opslog 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @backloghq/opslog 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @backloghq/opslog 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @backloghq/opslog 可以商用吗？

@backloghq/opslog 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @backloghq/opslog 的下载量有多少？

根据 npm 官方下载统计，@backloghq/opslog 在 2026/10/08 – 2026/11/06 这 30 天内被下载 2,059 次，日均约 69 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @backloghq/opslog 还在维护吗？

最新版本 0\.8\.1 发布于 2026/04/12（6 个月前）；至今共发布 16 个版本，其中过去 12 个月发布了 16 个；首次发布于 2026/04/07。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

