@animalabs/chronicle

中文文档
0.6.0发布于 2026/11/07MITTypeScriptCommonJS零依赖构建来源已验证近 30 天下载 2.5万

具有因果关系的分行旅行记录库

中文使用文档
文档目录13 节
  1. 安装
  2. 先看一眼,再看一眼
  3. 店里有这么多东西
  4. 记录
  5. 国家
  6. 分支
  7. 块
  8. 订阅
  9. 磁盘
  10. 从Rust取出
  11. 建设和贡献
  12. 文档
  13. 许可证

编年史

编年史(英語:listle)是一所以仅以附加记录为主的嵌入式商店,有复制于写作的分行和时间出行. 被命名的状态作为该日志的更改而保留,因此其中任何一种状态都可以像以前一样被读取,每个分支都会改变自己的状态. 大型数据或共享数据在一个内容地址的blob商店中。 核心是Rust图书馆,Node.js通过它使用 @animalabs/chronicle。 。 。 .

编年史是康乃馨的存储层: 代理框架, (中文(简体) ), 上下文管理器 财务报告和审定财务报表 连接主机 保存他们的记录,并在其中声明.

安装

npm install @animalabs/chronicle

该套件包括Linux(x64和arm64,glibc),macOS(x64和arm64)和Windows(x64)的预建本土模块. 在另一个平台上,从源头构建模块(参见 建设和贡献) (中文(简体) ).

先看一眼,再看一眼

在您安装了软件包的新目录中, 请保存为 example.mjs 运行 node example.mjs。 。 。 .

import { JsStore } from '@animalabs/chronicle';

// A new store: registerState and createBranch throw for a state or branch
// that already exists, so run this once, or delete ./my-store first.
const store = JsStore.openOrCreate({ path: './my-store' });

// Records: an append-only log. IDs are decimal strings.
const asked = store.appendJson('message', { from: 'user', text: 'Hello' });
const answered = store.appendJsonWithLinks('message', { from: 'agent', text: 'Hi!' }, { causedBy: [asked.id] });
store.getEffects(asked.id);                // [answered.id]

// States: named values kept as changes in the log.
store.registerState({ id: 'messages', strategy: 'append_log' });
store.appendToStateJson('messages', { text: 'Hello' });
const before = store.currentSequence();
store.appendToStateJson('messages', { text: 'Hi!' });
store.getStateJson('messages');            // [{ text: 'Hello' }, { text: 'Hi!' }]
store.getStateJsonAt('messages', before);  // [{ text: 'Hello' }]

// Branches: copy-on-write. A store starts with one branch, `main`.
store.createBranch('experiment');
store.switchBranch('experiment');
store.appendToStateJson('messages', { text: 'Only on experiment' });
store.switchBranch('main');
store.getStateJson('messages');            // still the two items

// Blobs: bytes stored under their SHA-256 hash.
const hash = store.storeBlob(Buffer.from('large data'), 'text/plain');
store.getBlob(hash);                       // the same bytes

store.close();

每一种方法都是同步的: 它运行在调用线条上 pollSubscriptionTimeout 屏蔽它直到超时。 包裹 index.d.ts从 src/napi.rs 列出所有方法及其选项.

店里有这么多东西

记录

每个记录都有一个ID,一个序列号,一个类型,一个有效载荷,一个时间戳,以及两个与其他记录的链接列表: causedBy 为它的原因 linkedTo 对于任何其他关系. append 以有效载荷为字节并 appendJson 作为JSON,和 WithLinks 每个链接的窗体. getEffects(id) 记录 causedBy 记录的名字,和 getLinksTo(id) (b) 其 linkedTo 已经.

getRecord 用身份证读取任何记录 getRecords(ids) 在一个呼叫中,按给出的顺序读取几个 null 对于一个没有记录的ID. getRecordIdsByType 列出一个类型在所有分支中的记录ID,按照上升的ID顺序,所以最后一个是最新的;ID是十进制字符串,所以把它们作为数字比较. 记录一个被撕裂了的尾巴滴出在开放, 和他们的ID再次发布到新的记录, 可能是另一种类型。 所以在坠机前的最后一张ID可以在之后命名不同的记录,然后取出一个伯爵就可以跳过新的记录. query 读取当前分支所附加的记录,最多按序列范围和类型分列 limit 他们中有10 000人.

国家

一个状态是一个被命名的值,其每个变化都是日志中的记录. 这就是让你读它 因为它在早期序列(getStateAt, (中文(简体) ), getStateJsonAt),并让每个分支分别修改. 以三种战略之一登记一个国家:

  • snapshot: 每个更改都存储整个值 (setState, (中文(简体) ), setStateJson) (中文(简体) ).

  • append_log: 您附加到的列表( s)appendToState, (中文(简体) ), appendToStateJson),按索引编辑(editStateItem)和按范围调整(redactStateItems) (中文(简体) ). 更改被存储为操作 。 一跑一跑的附子会得到周期性的三角洲快照,所以一读不会回放整个历史,随着列表的增长,完整的快照会更加相去甚远. deltaSnapshotEvery (默认100个)和 fullSnapshotEvery (10) 设定基准间隔. getStateLen, (中文(简体) ), getStateSlice, (中文(简体) ), getStateTail 财务报告和审定财务报表 getStateItemJson 读取列表的一部分,以及 getStateItemsJson(id, ordinals) 在一个呼叫中逐项读取.

    appendManyToStateJson(id, items) 以一个顺序将多个项目作为单项记录,然后以 firstOrdinal,它的第一个项目是: 清单的长度就在批次之前,因为 getStateLen 一旦批次数到项目(项目) i 处于 firstOrdinal + i;在一个其值不是JSON阵列的列表中,或者持有一个不属于JSON的物品,批量取出所储存的计数,被记录下来,然后状态未能实现,就像一个 appendToState ,所以项目不能 读在那些指数。 记录错误的0.1.0到0.4.x的计数,在开通后,在每家分店的第一批清单中重新列出 compactState。阅读器将每个项目视为单独附件,但历史读取或按顺序切出的一个分支保留或降下批次整体,因为它有一个序列,批次算作向快照间隔的一个操作。 坠机会保持或失去一个批量的完整, 从来没有一部分。 商店的第一批更改格式:见 MANIFEST 下级 磁盘。 。 。 .

    appendToStateJsonWithIdentity(id, item, idField, sequenceField) 附加一个 JSON 对象,并附有它写在这两个键上的记录的ID和顺序,因此,该物品在日志中带有它自己的位置. appendManyToStateJsonWithIdentity(id, items, idField, sequenceField) 是其分批的形式: 分批的每件物品都带有分批记录的ID和序列, 因此在分批物品的序列中切出一个分支保存整个批次, 在投放前一个分支保存一个序列. 它抛出和写什么 idField 财务报告和审定财务报表 sequenceField 名称为相同的密钥; 单式为这样的一对并留下该密钥的序列, 高于 id.

  • tree: 从路径到取名blobs的条目的地图(treeSet, (中文(简体) ), treeRemove, (中文(简体) ), treeGet, (中文(简体) ), treeList, (中文(简体) ), treeDiff等),默认情况下每50个操作有一个三角洲快照.

店里有登记 所以又开了一家店, registerState 扔出“ 状态已经存在 ” , 用于以前注册的状态 ; listStates() 列出他们.

registerStateFieldIndex 保留列表项目中的一个字段的索引,用于范围查询和平等查询(queryStateIndexRange, (中文(简体) ), queryStateIndexEq) (中文(简体) ). 它属于它注册的分行: 更改另一个分行的状态会掉下它, 而查询返回 null 直到它再次注册.

分支

createBranch(name, from) 在首端启动分支 from,或当 from 被排除在外. createBranchAt(name, from, sequence) 以较早的顺序开始 from一个新的分支开始于其母国的当时。 记录和状态变化到当前分支, 该分支 switchBranch 选择.

块

storeBlob 将字节保存在他们的SHA-256散列下,所以同一内容存储一次,以及 getBlob 读回来. getBlob 检查文件的校验和散列, 并扔出一个不全的文件; 当商店不再持有存储中的blob 时, 同样的内容会再次替换这样的文件 。 超过65,535字节的内容类型被拒绝. 一个开放的商店保留它最近写的或读取的blobs作为内存(blobCacheSize 其中,1000个默认)和从那里得到的答案,所以从店外对一个blob文件造成的损坏,只显示在blob离开缓存或商店重新开放时.

订阅

subscribe(config) 返回一个ID。 装扮的 filter 指定记录类型和接下来的状态,并打开您想要的每一种事件 includeRecords, (中文(简体) ), includeStateChanges 财务报告和审定财务报表 includeBranchEvents,除非设定,否则关闭。 还需要一个 branch现场活动可以从任何分支到达。 事件只涉及一个 已经赶上的订阅 catchUpSubscription 就是让事情发生 与一个 fromSequence 在配置中,它首先按当时的状态发送每个后续状态(或现在的状态,如果当时不存在),并重放当前分支自此所附加的匹配记录. 一个大于配置的状态 maxSnapshotBytes 剪切( 默认为 10 MiB) 以适应: 列表只发送其最新项目, 其它任何值发送 null。该活动 data一个JSON弦,然后 truncated 设定和列表, from_index 财务报告和审定财务报表 total_length 说哪部分到了 变化 subscribe 财务报告和审定财务报表 catchUpSubscription 只有在重播时才能到达 之后,每个 pollSubscription 返回下一个事件,或 null 当没有人等待的时候 pollSubscriptionTimeout 等待一个超时。 用户允许 bufferSize 事件(默认情况下为1,000起) 堆积起来的无保值被删除.

磁盘

商店是一个目录。 日志是 records.log,州链的头在 state.bin字段索引 state-indexes.bin,在 branches.bin下方的块 blobs/。 。 。 . MANIFEST 命名格式版本: 1, 每次发行打开它, 直到它移动到 2 , 就在商店的第一个批次附件之前(appendManyToStateJson 或者说 appendManyToStateJsonWithIdentity,或 AppendMany 从Rust)所写的. 从那时起,它就保持2, 即使这批失败, 并释放那些 先前的批次附件 拒绝打开商店 MANIFEST (对于一个链接的,持有它的目标的)是同步的(在Windows上,这个进程可能不打开该目录,或者在一个不同步目录的系统上,一个功率损失可以撤销移动到版本2并保留批次;见"在更改日志中断开";如果移动本身失败,调用和批次没有被记录下来,如果移动失败后只有目录同步,它也会抛出 MANIFEST 已经在第2版了. create 拒绝已经持有商店的目录, 因此它从来不写商店的 MANIFEST 返回到 1: 打开可能存在的商店 openOrCreate 或者说 open。 。 。 . LOCK 在商店开放时持有. LOCK 必须是正则文件, 或缺失: 打开并创建拒绝链接或其他任何内容 。 不要删除或替换 LOCK 当商店打开时: 下一个打开会锁起新文件, 并打开商店 .

  • 一次一个过程. 店开着,又开着 open 它与“ Store 被另一个进程锁定” 失败。 与 LOCK 一个正则文件,一个由于任何其他原因而失败的锁会因 I/O 错误而失败.

  • 杜尔比特. 日志被冲入每100个记录的磁盘中,因此机器崩溃可能损失到最后100个. sync() 财务报告和审定财务报表 close() 冲去保存 state.bin 财务报告和审定财务报表 branches.bin,每个字写入一个新的文件,然后替换旧文件,所以在 sync() 保存之前或之后保存每个文件。 注册状态,改变策略,并在返回前创建、删除或切换分支. storeBlob 将 blob 写入新文件 blobs/,将其冲出并重命名为返回前的位置,所以一个崩溃会留下文件整体,或者像呼叫前一样(缺席,或者它正在替换被损坏的复制件). 当一个进程被杀死或提前退出时 close() (坠机,Ctrl-C等信号,或 process.exit()),下个开口截取了部分被写入的上个记录,将每个分支的头抬到日志上,并重放状态自上个保存后更改了日志所持有,但保存的分支上保存的则不持有. recovery() 报告它发现了什么。 打开也会删除新文件( O).chronicle-blob-* 内 blobs/在重名前被切断的blob商店。 一旦日志的同步失败, sync() 丢出错误开始 Durability lost: 直到店铺重新开业, 上面的电话也一样,在返回之前。 打电话 close() 离开之前,和 sync() 更改后,您不能输.

  • 看着里面. 存储为二进制. tools/ 有读取的 Python 脚本 records.log {\fn黑体\fs20\shad2\2ah82\3ah20\4ah33\fscx95\3ch592001\be1}直接地,没有人会拿走.. LOCK在经营写作机前关闭商店.

    • trim-records-log.py <store>/records.log 报告一条被撕裂的尾巴 用它修剪 --apply 过道后.
    • scan-delta-poison.py 检查 append_log 毒药三角洲的快照, rebuild-state-index.py 重写 state.bin 从日志,和 chain-report.py 报告命名状态的更新链 messages。这三人读取状态变化为 JSON, 0: 3: 0 之前的编码, 并用一个自此写出的错误止步, 这是 MessagePack .

    server/ 是快取的中间软件, Vue 应用程序在其中 ui/浏览商店 Npm 包里也没有.

从Rust取出

图书馆是 chronicle 装入箱子 src/它不在箱子上, io,在哪里 chronicle 这是另一个项目,所以依赖它从Git:

[dependencies]
chronicle = { git = "https://github.com/anima-research/chronicle" }
serde_json = "1"
use chronicle::{RecordInput, Store, StoreConfig};
use serde_json::json;

fn main() -> chronicle::Result<()> {
    let store = Store::open_or_create(StoreConfig {
        path: "./my-store".into(),
        ..Default::default()
    })?;
    let record = store.append(RecordInput::json("message", &json!({ "text": "Hello" }))?)?;
    println!("record {} at sequence {}", record.id.0, record.sequence.0);
    Ok(())
}

examples/store_probe.rs量度状态读取在真实商店的副本上 也可以直接使用箱子 节点绑定是 src/napi.rs,与 napi-bindings 特性.

建设和贡献

捐款额 涵盖建筑和测试cargo test,本地模块的构造,和 npm test),拉动请求应包含什么,更改日志片段在 changelog.d/并释放.

文档

许可证

麻省理工学院,作为 package.json 财务报告和审定财务报表 Cargo.toml 声明.

版本与发布记录

@animalabs/chronicle 共发布 11 个版本,首次发布于 2026/03/31;过去 12 个月发布了 11 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.0latest2026/11/07今天
0.5.12026/10/308 天前
0.5.02026/10/299 天前
0.4.02026/09/171 个月前
0.3.02026/08/023 个月前
0.2.72026/07/153 个月前

关于 @animalabs/chronicle 的常见问题

@animalabs/chronicle 是什么?

具有因果关系的分行旅行记录库

如何安装 @animalabs/chronicle?

在项目目录运行 npm install @animalabs/chronicle,或使用 pnpm add / yarn add / bun add。

@animalabs/chronicle 支持 TypeScript 吗?

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

@animalabs/chronicle 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@animalabs/chronicle 需要什么版本的 Node.js?

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

@animalabs/chronicle 可以商用吗?

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

@animalabs/chronicle 的下载量有多少?

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

@animalabs/chronicle 还在维护吗?

最新版本 0.6.0 发布于 2026/11/07(今天);至今共发布 11 个版本,其中过去 12 个月发布了 11 个;首次发布于 2026/03/31。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。