Skip to content

Commit 8a06ad7

Browse files
committed
fix: build err
1 parent 7c0a521 commit 8a06ad7

File tree

4 files changed

+167
-38
lines changed

4 files changed

+167
-38
lines changed

pages/docs/api/js.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Note: Under the hood, Loro combines a Fugue-based CRDT core with Eg-walker-inspi
3737
- Initialize all child containers for a LoroMap upfront when possible
3838
- Operations on the root containers will not override each other
3939

40-
- Events emit synchronously during commit/import/checkout in JS API (v1.8+). Stay on <=1.7.x? Await a microtask before reading batched events.
40+
- Events emit synchronously during commit/import/checkout in JS API (v1.8+). Stay on `<=1.7.x`? Await a microtask before reading batched events.
4141
- Import/export/checkout trigger automatic commits
4242
- Loro transactions are NOT ACID - no rollback/isolation
4343

@@ -842,7 +842,7 @@ const unsubscribe = doc.subscribe((event) => {
842842
// Later: unsubscribe();
843843
```
844844

845-
**⚠️ Important:** Events are emitted synchronously as of v1.8. If you are pinned to <=1.7.x, await a microtask before reading the batch.
845+
**⚠️ Important:** Events are emitted synchronously as of v1.8. If you are pinned to `<=1.7.x`, await a microtask before reading the batch.
846846
```ts no_run
847847
doc.commit();
848848
// Events have already been delivered in v1.8+

pages/docs/tutorial/event.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ with changes in the document state.
1919
- When `LoroDoc.commit()` is explicitly called
2020
- Automatically before an import or export operation
2121

22-
Starting from `[email protected]`, events are emitted synchronously during the commit cycle. If you are using an older version (<=1.7.x), you will still need to await a microtask for the callbacks to fire.
22+
Starting from `[email protected]`, events are emitted synchronously during the commit cycle. If you are using an older version (`<=1.7.x`), you will still need to await a microtask for the callbacks to fire.
2323

2424
```ts no_run twoslash
2525
import { LoroDoc } from "loro-crdt";

public/blog.xml

Lines changed: 128 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,95 @@
44
<title>Loro Blog</title>
55
<link>https://loro.dev/blog/</link>
66
<description>Updates and stories from the Loro team.</description>
7-
<lastBuildDate>Mon, 22 Sep 2025 13:16:41 GMT</lastBuildDate>
7+
<lastBuildDate>Mon, 10 Nov 2025 08:29:34 GMT</lastBuildDate>
88
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
99
<generator>https://github.com/jpmonette/feed</generator>
10+
<item>
11+
<title><![CDATA[Loro Protocol]]></title>
12+
<link>https://loro.dev/blog/loro-protocol</link>
13+
<guid>https://loro.dev/blog/loro-protocol</guid>
14+
<pubDate>Wed, 29 Oct 2025 16:00:00 GMT</pubDate>
15+
<description><![CDATA[The Loro Protocol multiplexes CRDT sync workloads over one WebSocket connection and ships the open-source loro-websocket, loro-adaptors, plus Rust client and server implementations that speak the same protocol.]]></description>
16+
<content:encoded><![CDATA[<h2>Loro Protocol</h2>
17+
<p><img src="/images/blog-loro-protocol.png" alt=""></p>
18+
<p>The open-source Loro Protocol project includes the <code>loro-websocket</code> package, the adaptor suite in <code>loro-adaptors</code>, and matching Rust client and server implementations that all interoperate on the same wire format.</p>
19+
<p>The <a href="https://github.com/loro-dev/protocol"><strong>Loro Protocol</strong></a> is a wire protocol designed for real-time CRDT synchronization. Learn about the design in detail <a href="https://github.com/loro-dev/protocol/blob/main/protocol.md">here</a>.</p>
20+
<p>It efficiently runs multiple, independent &quot;rooms&quot; over a single WebSocket connection.</p>
21+
<p>This allows you to synchronize your application state, such as a Loro document, ephemeral cursor positions, and end-to-end encrypted documents, over one connection. It is also compatible with Yjs.</p>
22+
<h3>Quick Start: Server &amp; Client Example</h3>
23+
<p>The protocol is implemented by the <code>loro-websocket</code> client and a minimal <code>SimpleServer</code> for testing. These components are bridged to your CRDT state using <code>loro-adaptors</code>.</p>
24+
<p><strong>Server</strong></p>
25+
<p>For development, you can run the <code>SimpleServer</code> (from <code>loro-websocket</code>) in a Node.js environment.</p>
26+
<pre><code class="language-tsx">// server.ts
27+
28+
const server = new SimpleServer({
29+
port: 8787,
30+
// SimpleServer accepts hooks for authentication and data persistence:
31+
// authenticate: async (roomId, crdt, auth) =&gt; { ... },
32+
// onLoadDocument: async (roomId, crdt) =&gt; { ... },
33+
// onSaveDocument: async (roomId, crdt, data) =&gt; { ... },
34+
});
35+
36+
server.start().then(() =&gt; {
37+
console.log(&quot;SimpleServer listening on ws://localhost:8787&quot;);
38+
});
39+
</code></pre>
40+
<p><strong>Client</strong></p>
41+
<p>On the client side, you connect once and then join multiple rooms using different adaptors.</p>
42+
<pre><code class="language-tsx">// client.ts
43+
44+
// 1. Create and connect the client
45+
const client = new LoroWebsocketClient({ url: &quot;ws://localhost:8787&quot; });
46+
await client.waitConnected();
47+
console.log(&quot;Client connected!&quot;);
48+
49+
// --- Room 1: A Loro Document (%LOR) ---
50+
const docAdaptor = new LoroAdaptor();
51+
const docRoom = await client.join({
52+
roomId: &quot;doc:123&quot;,
53+
crdtAdaptor: docAdaptor,
54+
});
55+
56+
// Local edits are now automatically synced
57+
const text = docAdaptor.getDoc().getText(&quot;content&quot;);
58+
text.insert(0, &quot;Hello, Loro!&quot;);
59+
docAdaptor.getDoc().commit();
60+
61+
// --- Room 2: Ephemeral Presence (%EPH) on the SAME socket ---
62+
const ephAdaptor = new LoroEphemeralAdaptor();
63+
const presenceRoom = await client.join({
64+
roomId: &quot;doc:123&quot;, // Can be the same room ID, but different magic bytes
65+
crdtAdaptor: ephAdaptor,
66+
});
67+
68+
// Ephemeral state syncs, but is not persisted by the server
69+
ephAdaptor.getStore().set(&quot;cursor&quot;, { x: 100, y: 100 });
70+
</code></pre>
71+
<hr>
72+
<h3>Features</h3>
73+
<h4>Multiplexing</h4>
74+
<p>Each binary message is prefixed with four magic bytes that identify the data type, followed by the <code>roomId</code>. This structure allows the server to route messages to the correct handler. A single client can join:</p>
75+
<ul>
76+
<li><code>%LOR</code> (Loro Document)</li>
77+
<li><code>%EPH</code> (Loro Ephemeral Store, for cursors and presence)</li>
78+
<li><code>%ELO</code> (End-to-End Encrypted Loro Document)</li>
79+
<li><code>%YJS</code> and <code>%YAW</code> (for Yjs Document and Awareness interoperability)</li>
80+
</ul>
81+
<p>All traffic runs on the same socket.</p>
82+
<h4>Compatibility</h4>
83+
<p>The Loro Protocol is designed to accommodate environments like Cloudflare:</p>
84+
<ul>
85+
<li>Fragmentation: Large updates are automatically split into fragments under 256 KiB and reassembled by the receiver. This addresses platforms that enforce WebSocket message size limits.</li>
86+
<li>Application-level keepalive: The protocol defines simple <code>&quot;ping&quot;</code> and <code>&quot;pong&quot;</code> text frames. These bypass the binary envelope and allow the client to check connection liveness, which is useful in browser or serverless environments where transport-level TCP keepalives are not exposed.</li>
87+
</ul>
88+
<p>This repository also ships Rust clients and servers that mirror the TypeScript packages.</p>
89+
<h4>Experimental E2E Encryption</h4>
90+
<p>End-to-end encrypted Loro is included in <code>loro-protocol</code>, but the feature is currently experimental: expect wire formats and key-management APIs to change, and do not rely on it for production-grade security audits yet. When paired with <code>EloLoroAdaptor</code> on the client, the server relays encrypted records without decrypting them.</p>
91+
<h3>Status and Licensing</h3>
92+
<p>The Loro Protocol is mostly stable. We welcome community feedback and contributions, especially regarding use cases that are difficult to satisfy with the current design.</p>
93+
<p>All the packages in inside <a href="https://github.com/loro-dev/protocol">https://github.com/loro-dev/protocol</a> are open-sourced under the permissive MIT license.</p>
94+
]]></content:encoded>
95+
</item>
1096
<item>
1197
<title><![CDATA[Loro Mirror: Make UI State Collaborative by Mirroring to CRDTs]]></title>
1298
<link>https://loro.dev/blog/loro-mirror</link>
@@ -339,11 +425,10 @@ console.log(docB.getText(&quot;text&quot;).toString()); // Hello!Hi!
339425
</details>
340426
</details>
341427
342-
343428
<h2>Features of Loro 1.0</h2>
344429
<h3>High-performance CRDTs</h3>
345430
<p>High-performance, general-purpose CRDTs can significantly reduce data synchronization
346-
complexity and are crucial for local-first development. </p>
431+
complexity and are crucial for local-first development.</p>
347432
<p>However, large CRDT documents may face challenges with loading speed and memory consumption,
348433
especially when dealing with those with extensive editing histories.
349434
Loro 1.0 addresses this challenge through a new storage format, achieving a 10x improvement in
@@ -402,7 +487,7 @@ firstNote.insert(0, &quot;Hello, world!&quot;);
402487
console.log(doc.toJSON());
403488
</code></pre>
404489
<h3>Version control</h3>
405-
<p>Like Git, Loro saves a complete directed acyclic graph (DAG) of edit history. In Loro, the DAG is used to represent the dependencies between edits, similar to how Git represents commit history. </p>
490+
<p>Like Git, Loro saves a complete directed acyclic graph (DAG) of edit history. In Loro, the DAG is used to represent the dependencies between edits, similar to how Git represents commit history.</p>
406491
<p>Loro supports primitives that allow users to switch between different versions, fork new branches, edit on new branches, and merge branches.</p>
407492
<p>Based on this operation primitive, applications can build various Git-like capabilities:</p>
408493
<ul>
@@ -474,7 +559,7 @@ solved through additional libraries.</p>
474559
475560
<h3>Leveraging the potential of the <a href="https://arxiv.org/abs/2409.14252">Eg-walker</a></h3>
476561
<p><a href="/docs/advanced/event_graph_walker">Event Graph Walker (Eg-walker)</a> is a pioneering collaboration algorithm that combines the strengths of
477-
Operational Transformation (OT) and CRDT, two widely used algorithms for real-time collaboration. </p>
562+
Operational Transformation (OT) and CRDT, two widely used algorithms for real-time collaboration.</p>
478563
<p>While OT is centralized and CRDT is decentralized, OT traditionally had an advantage
479564
in terms of lower document overhead. CRDTs initially had higher overhead, but recent
480565
optimizations have significantly reduced this gap, making CRDTs increasingly competitive.
@@ -570,12 +655,12 @@ an example usage:</p>
570655
<pre><code class="language-jsx">
571656
const doc = new LoroDoc();
572657
for (let i = 0; i &lt; 10_000; i++) {
573-
doc.getText(&quot;text&quot;).insert(0, &quot;Hello, world!&quot;);
658+
doc.getText(&quot;text&quot;).insert(0, &quot;Hello, world!&quot;);
574659
}
575660
const snapshotBytes = doc.export({ mode: &quot;snapshot&quot; });
576661
const shallowSnapshotBytes = doc.export({
577-
mode: &quot;shallow-snapshot&quot;,
578-
frontiers: doc.frontiers(),
662+
mode: &quot;shallow-snapshot&quot;,
663+
frontiers: doc.frontiers(),
579664
});
580665
581666
console.log(snapshotBytes.length); // 5421
@@ -587,7 +672,7 @@ console.log(shallowSnapshotBytes.length); // 869
587672
<p>Loro version 1.0 has achieved a 10x to 100x improvement in document import speed
588673
compared to version 0.16, which already has a fast import speed.
589674
It makes it possible to load a large text document with several million operations
590-
in under a frame time. </p>
675+
in under a frame time.</p>
591676
<p>This is because we introduced a new snapshot format.
592677
When a LoroDoc is initialized through this snapshot format, we don&#39;t
593678
parse the corresponding document state and historical information until the user
@@ -615,7 +700,7 @@ state of the document (the old version&#39;s encoding learned from
615700
because import speed affects the performance of many aspects, and the import
616701
speed of CRDT documents
617702
<a href="https://loro.dev/docs/performance">is often noticeable to users on large documents</a>
618-
(&gt; 16ms). It also leaves possibilities for more optimizations in the future. </p>
703+
(&gt; 16ms). It also leaves possibilities for more optimizations in the future.</p>
619704
<aside>
620705
❓ **Does this affect the efficiency of data transmission?**
621706
@@ -641,10 +726,12 @@ speed of CRDT documents
641726
<li><p>For local storage:</p>
642727
<ul>
643728
<li>Users are generally less sensitive to local storage costs.</li>
644-
<li>The snapshot format can be used for local persistence without significant impact.</aside></li>
729+
<li>The snapshot format can be used for local persistence without significant impact.</li>
645730
</ul>
646731
</li>
647732
</ol>
733+
</aside>
734+
648735
<p>Inspired by the design of Key-Value Databases, we have also divided the storage
649736
of document state and history into blocks, with each block roughly 4KB in size,
650737
so that when users really need a piece of history, we only need to decompress
@@ -898,21 +985,22 @@ data, but can solve semantic automatic merging of JSON-like schema, which can me
898985
most needs of creative tools and collaborative tools.</li>
899986
</ul>
900987
<p>We&#39;ve created a demo of the Loro version controller, which is based on our
901-
sub-document implementation (implemented in the application layer) with Version information.
988+
sub-document implementation (implemented in the application layer) with Version information.
902989
It can import the entire React repository (about 20,000 commits, thousands of
903990
collaborators), and it supports real-time collaboration on such
904991
repositories. However, how to better manage versions and seamlessly integrate with Git still needs to be explored.</p>
905992
<aside>
906-
When merging extensive concurrent edits, CRDTs can automatically merge changes,
907-
but the result may not always meet expectations. Fortunately, Loro stores the
908-
complete editing history. This allows us to offer Git-like manual conflict
909-
resolution at the application layer when needed.
993+
When merging extensive concurrent edits, CRDTs can automatically merge
994+
changes, but the result may not always meet expectations. Fortunately, Loro
995+
stores the complete editing history. This allows us to offer Git-like manual
996+
conflict resolution at the application layer when needed.
910997
</aside>
911998
912999
<p>Loro CRDTs still have significant room for optimization in these scenarios.
9131000
Currently, the Loro CRDTs library doesn&#39;t involve network or disk I/O, which
9141001
enhances its ease of use but also constrains its capabilities and potential
915-
optimizations.<br>For example, while we&#39;ve implemented block-level storage, documents are still
1002+
optimizations.
1003+
For example, while we&#39;ve implemented block-level storage, documents are still
9161004
imported and exported as whole units. Adding I/O capabilities to selectively
9171005
load/save blocks would enable significant performance optimizations.</p>
9181006
<h2>Conclusion</h2>
@@ -924,8 +1012,10 @@ We&#39;re excited to see it being applied in various scenarios.
9241012
If you&#39;re interested in using Loro, welcome to join our
9251013
<a href="https://discord.gg/tUsBSVfqzf">Discord community</a> for discussions.</p>
9261014
<aside>
927-
🚀 **Want early access to our upcoming local-first apps built with Loro?**
928-
[Sign up here](https://noteforms.com/forms/request-early-access-for-loro-apps-vkbt9p) to be among the first to try them out!
1015+
🚀 **Want early access to our upcoming local-first apps built with Loro?**
1016+
[Sign up
1017+
here](https://noteforms.com/forms/request-early-access-for-loro-apps-vkbt9p)
1018+
to be among the first to try them out!
9291019
</aside>]]></content:encoded>
9301020
</item>
9311021
<item>
@@ -1270,7 +1360,8 @@ CRDTs. Thus, the simplest solution is not to perform any tombstone collection.</
12701360
<h3>Brief Introduction to Event Graph Walker</h3>
12711361
<p>Eg-walker is a novel CRDT algorithm introduced in:</p>
12721362
<blockquote>
1273-
<p><a href="https://arxiv.org/abs/2409.14252">Collaborative Text Editing with Eg-walker: Better, Faster, Smaller</a><br>By: Joseph Gentle, Martin Kleppmann</p>
1363+
<p><a href="https://arxiv.org/abs/2409.14252">Collaborative Text Editing with Eg-walker: Better, Faster, Smaller</a>
1364+
By: Joseph Gentle, Martin Kleppmann</p>
12741365
</blockquote>
12751366
<p>Eg-walker is a novel CRDT algorithm that combines the strengths of both OT and CRDTs.
12761367
It has the distributed nature of CRDT that enables P2P collaboration and data
@@ -1924,7 +2015,7 @@ any proposals for collaboration, please reach out to <a href="mailto:[email protected]
19242015
<div style={{ display: "inline" }}>
19252016
Loro, our high-performance CRDTs library, is now open source
19262017
</div>
1927-
2018+
19282019
<div style={{ display: "inline" }}>.</div>
19292020
</div>
19302021
@@ -2010,17 +2101,20 @@ state management tool, Pinia:</p>
20102101
}),
20112102
getters: {
20122103
items: (state): Array&lt;{ name: string; amount: number }&gt; =&gt;
2013-
state.rawItems.reduce((items, item) =&gt; {
2014-
const existingItem = items.find((it) =&gt; it.name === item);
2015-
2016-
if (!existingItem) {
2017-
items.push({ name: item, amount: 1 });
2018-
} else {
2019-
existingItem.amount++;
2020-
}
2021-
2022-
return items;
2023-
}, [] as Array&lt;{ name: string; amount: number }&gt;),
2104+
state.rawItems.reduce(
2105+
(items, item) =&gt; {
2106+
const existingItem = items.find((it) =&gt; it.name === item);
2107+
2108+
if (!existingItem) {
2109+
items.push({ name: item, amount: 1 });
2110+
} else {
2111+
existingItem.amount++;
2112+
}
2113+
2114+
return items;
2115+
},
2116+
[] as Array&lt;{ name: string; amount: number }&gt;,
2117+
),
20242118
},
20252119
actions: {
20262120
addItem(name: string) {
@@ -2064,7 +2158,7 @@ collaboration and time machine features.</p>
20642158
whole history and playback, it only takes 8.4MB in memory. And the entire
20652159
history only takes 361KB in storage. The editing trace is from{" "}
20662160
</div>
2067-
2161+
20682162
<div style={{ display: "inline" }}>.</div>
20692163
20702164
@@ -2538,4 +2632,4 @@ We simulate one client replaying all changes and storing each update. We measure
25382632
]]></content:encoded>
25392633
</item>
25402634
</channel>
2541-
</rss>
2635+
</rss>

0 commit comments

Comments
 (0)