文档教程Loro 文档

LoroDoc 入门

LoroDoc 是使用 Loro 的主要入口,负责容器管理、版本控制、事件系统以及导入导出等功能。

它提供:

  1. 容器管理——创建并管理 Text、List、Map、Tree、MovableList 等 CRDT 容器
  2. 版本控制——追踪历史、检出版本、管理分支
  3. 事件系统——订阅文档级或容器级的变更
  4. 导入导出——以多种格式保存与加载文档或增量

基础用法

首先创建一个 LoroDoc 实例:

// 使用随机 PeerID 创建文档
const  = new ();
 
// 也可以设置固定 PeerID
.("1");
 
// 获取各类容器
const  = .("text");
const  = .("list");
const  = .("map");
const  = .("tree");
const  = .("tasks");

假设要建模如下结构:

{
  "meta": {
    "title": "Document Title",
    "createdBy": "Author"
  },
  "content": "Article",
  "comments": [
    {
      "user": "userId",
      "comment": "comment"
    }
  ]
}
const  = new ();
const  = .("meta");
.("title", "Document Title");
.("createdBy", "Author");
.("content").(0, "Article");
const  = .("comments");
const  = .(0, new ());
.("user", "userId");
.("comment", "comment");

支持的数据类型(LoroValue)

LoroValue 表示 Loro 容器可直接存储的类型:

type LoroValue =
  | null
  | boolean
  | number
  | string
  | Uint8Array
  | LoroValue[]
  | { [key: string]: LoroValue };

存储不同数据类型的示例

const  = new ();
const  = .("data");
 
// 基本类型
.("name", "Alice");
.("age", 30);
.("score", 95.5);
.("isActive", true);
.("metadata", null);
 
// 二进制数据
.("bytes", new ([1, 2, 3, 4, 5]));
 
// 嵌套对象
.("profile", {
  : "[email protected]",
  : {
    : "dark",
    : true,
  },
});
 
// 数组
.("tags", ["javascript", "typescript", "loro"]);
 
// 混合结构
.("complex", {
  : [1, 2, { : true }],
  : new ([255, 128]),
  : false,
});

如果需要在容器内嵌套另一个 CRDT 容器(例如 LoroText、LoroList),请使用 setContainer()insertContainer(),而不是普通的 set/insert

容器类型

LoroDoc 支持以下容器:

  1. Text:富文本编辑
  2. List:有序集合
  3. Map:键值对
  4. Tree:树形结构
  5. MovableList:可移动列表

Text

const  = new ();
const  = .("text");
.(0, "Hello");
.(5, " World!");
.(.()); // "Hello World!"
 
// 富文本
.({
  : { : "after" },
  : { : "none" },
});
.({ : 0, : 5 }, "bold", true);

List

const  = new ();
const  = .("list");
.(0, "first");
.(1, "second");
.(.()); // ["first", "second"]
 
const  = .(2, new ());
.(0, "nested text");

Map

const  = new ();
const  = .("map");
.("name", "John");
.("age", 30);
.(.("name")); // "John"
 
const  = .("bio", new ());
.(0, "Software Engineer");

Tree

const  = new ();
const  = .("tree");
const  = .();
..("name", "Root");
 
const  = .();
..("name", "Child 1");
 
const  = .();
..("name", "Child 2");

MovableList

const  = new ();
const  = .("tasks");
.(0, "Task 1");
.(1, "Task 2");
.(0, 1); // 将 Task 1 移到 Task 2 之后

协作同步

const  = new ();
.("1");
const  = .("text");
 
const  = new ();
.("2");
const  = .("text");
 
.(() => {
  .();
});
 
.(() => {
  .();
});
 
.(0, "Hello");
.();
await .();
.(5, " World!");
.();

撤销 / 重做

const  = new ();
const  = new (, {
  : 100,
  : 1000,
});
 
const  = .("text");
.(0, "Hello");
.();
 
if (.()) {
  .();
}
if (.()) {
  .();
}

导入与导出

const  = new ();
const  = .({ : "snapshot" });
 
const  = .();
 
const  = new ();
.();

浅快照

const  = new ();
const  = .({
  : "shallow-snapshot",
  : .(),
});
 
const  = .();
const  = .();
const  = .();

浅快照只包含某一版本点之后的历史,适合减小数据体积或隐藏旧历史。

脱敏敏感内容

const  = new ();
.("1");
 
const  = .("text");
.(0, "Sensitive information");
.();
 
const  = .("map");
.("password", "secret123");
.("public", "public information");
.();
 
const  = .();
const  = {
  "1": [0, 21],
};
const  = (, );
const  = new ();
.();
 
.(.("text").());
// "���������������������"

脱敏会替换文本为占位符,Map/List 插入值为 null,但保留结构与元数据。若需彻底移除历史,请结合浅快照。

记得将脱敏后的文档同步给所有节点,否则仍可能存在包含敏感信息的旧副本。

订阅事件

const  = new ();
.(() => {
  .("Document changed:", );
});
 
const  = .("text");
.(() => {
  .("Text changed:", );
});

事件触发

事件在事务提交后、微任务结尾时触发:

const  = new ();
const  = .("text");
 
.(() => {
  .("Change event:", );
});
 
.(0, "Hello");
.();
await .();

导出、导入、checkout 等操作会隐式提交:

const  = new ();
 
.({ : "snapshot" });
.();
.();

提交时可以携带额外信息:

const  = new ();
.({
  : "user-edit",
  : "Add greeting",
  : .(),
});
await .();

版本控制与历史

版本表示

  1. 版本向量(peer → counter):
const  = new ();
const  = .();
const  = .();
  1. Frontiers(各 peer 最新操作 ID,通常只有 1 个元素):
const  = new ();
.("0");
.("map").("text", "Hello");
const  = .();
const  = .();

checkout 与时光旅行

const  = new ();
const  = .();
const  = .("text");
.(0, "Hello World!");
 
.();
.();
// 或 doc.attach();

checkout 后文档进入 detached 状态:默认不可编辑、导入的操作不会立即应用,需要 attach() 回到最新版本。

可以显式控制 detached:

const  = new ();
.(.()); // false
 
.();
.(.()); // true
 
.();
.(.()); // false
 
.(true);
.(.()); // true

导入行为

const  = new ();
.("map").("name", "John");
const  = .({ : "update" });
const  = new ();
 
.(); // 若处于 detached,仅记录,不立即应用
.(); // 应用更新

fork

const  = new ();
.("0");
.("text").(0, "Hello");
const  = .();
const  = .([{ : "0", : 1 }]);
.(.("text").()); // "He"

订阅与同步

本地更新订阅

const  = new ();
const  = .(() => {
  .();
});
 
();

文档事件

const  = new ();
.((: LoroEventBatch) => {
  .("触发来源:", .);
  .("origin:", .);
  for (const  of .) {
    .("容器:", .);
    .("路径:", .);
    .("diff:", .);
  }
});

指定容器事件

const  = new ();
const  = .("text");
.(() => {
  .("Text changed:", );
});
 
const  = .("list");
.(() => {
  .("List changed:", );
});

高级能力

稳定光标

const  = new ();
const  = .("text");
.(0, "123");
 
const  = .(0, 0);
if () {
  const  = .();
  .(.);
  .(.);
 
  .(0, "abc");
  const  = .();
  .(.);
}

变更追踪

const  = new ();
.("1");
.("text").(0, "Hello");
.();
.(.());
.(.());
 
const  = .();
for (const [, ] of .()) {
  for (const  of ) {
    .({
      : .,
      : .,
      : .,
      : .,
      : .,
    });
  }
}
 
const  = { : "1", : 0 } as ;
const  = .();
const  = .();
 
.([], () => {
  .("Ancestor:", );
  return true;
});
 
const  = .(, 1);

导入 / 导出模式

const  = new ();
const  = .();
.("text").(0, "Hello");
const  = .({ : "snapshot" });
const  = .({ : "update", :  });
const  = .({
  : "shallow-snapshot",
  : .(),
});
const  = .({
  : "updates-in-range",
  : [{ : { : "1", : 0 }, : 10 }],
});
 
const  = .();
.(.);
.(.);
 
const  = .([, ]);
 
const  = .({
  : 1,
  : new ([["1", 0]]),
  : ["1"],
  : [],
});

路径访问

const  = new ();
const  = .("map/key");
const  = .("list");
 
const  = .("cid:root-list:List");
 
const  = .("$.list[*]");
 
const  = .();
.();
 
const  = .((, ) => {
  if ( instanceof ) {
    return .();
  }
  return ;
});

调试与元信息

const  = new ();
();
const  = .({ : "update" });
const  = (, true);
.({
  : .,
  : .,
  : .,
  : .,
});