Skip to content

Commit 467358c

Browse files
committed
docs: add llms tips
1 parent a9b4c1e commit 467358c

3 files changed

Lines changed: 362 additions & 0 deletions

File tree

pages/docs/tutorial/tips.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,12 @@ This approach allows you to:
256256
The mapping is stored within the document, so it automatically synchronizes across all peers and persists with the document's state.
257257

258258
</details>
259+
260+
---
261+
262+
##### You can use https://loro.dev/llms-full.txt to prompt your AI
263+
264+
When working with AI assistants or language models on Loro-related tasks, you can use these URLs to provide comprehensive context about Loro's capabilities and API:
265+
266+
- `https://loro.dev/llms-full.txt` - All the documentation in one file
267+
- `https://loro.dev/llms.txt` - An overview of Loro website

public/llms-full.txt

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,6 +1988,195 @@ doc.import(docB.export({ mode: "update" }));
19881988
- You can use `doc.getMap("user." + userId)` instead of `doc.getMap("user").getOrCreateContainer(userId, new LoroMap())` to avoid this problem.
19891989
</details>
19901990

1991+
---
1992+
1993+
##### Use redaction to safely share document history
1994+
1995+
There are times when users might accidentally paste sensitive information (like API keys, passwords, or personal data) into a collaborative document. When this happens, you need a way to remove just that sensitive content from the document history without compromising the rest of the document's integrity.
1996+
1997+
<details>
1998+
<summary>How to safely redact sensitive content</summary>
1999+
2000+
Loro provides a `redactJsonUpdates` function that allows you to selectively redact operations within specific version ranges.
2001+
2002+
For example, if a user accidentally pastes a password or API key into a document:
2003+
2004+
```typescript
2005+
const doc = new LoroDoc();
2006+
doc.setPeerId("1");
2007+
2008+
// Create some content to be redacted
2009+
const text = doc.getText("text");
2010+
text.insert(0, "Sensitive information");
2011+
doc.commit();
2012+
2013+
const map = doc.getMap("map");
2014+
map.set("password", "secret123");
2015+
map.set("public", "public information");
2016+
doc.commit();
2017+
2018+
// Export JSON updates
2019+
const jsonUpdates = doc.exportJsonUpdates();
2020+
2021+
// Define version range to redact (redact the text content)
2022+
const versionRange = {
2023+
"1": [0, 21] // Redact the "Sensitive information"
2024+
};
2025+
2026+
// Apply redaction
2027+
const redactedJson = redactJsonUpdates(jsonUpdates, versionRange);
2028+
2029+
// Create a new document with redacted content
2030+
const redactedDoc = new LoroDoc();
2031+
redactedDoc.importJsonUpdates(redactedJson);
2032+
2033+
// The text content is now redacted with replacement characters
2034+
console.log(redactedDoc.getText("text").toString());
2035+
// Outputs: "���������������������"
2036+
2037+
// You can also redact specific map entries
2038+
const versionRange2 = {
2039+
"1": [21, 22] // Redact the "secret123" password
2040+
};
2041+
2042+
const redactedJson2 = redactJsonUpdates(jsonUpdates, versionRange2);
2043+
const redactedDoc2 = new LoroDoc();
2044+
redactedDoc2.importJsonUpdates(redactedJson2);
2045+
2046+
console.log(redactedDoc2.getMap("map").get("password")); // null
2047+
console.log(redactedDoc2.getMap("map").get("public")); // "public information"
2048+
```
2049+
2050+
This approach is safer than manually editing document content because:
2051+
2052+
1. It maintains document structure and CRDT consistency
2053+
2. It keeps key metadata like operation IDs and dependencies intact
2054+
3. It allows concurrent editing to continue working after redaction
2055+
4. It selectively redacts only specific operations, not the entire document
2056+
2057+
The redaction process follows these rules:
2058+
- Preserves delete, tree move, and list move operations
2059+
- Replaces text insertion content with Unicode replacement characters '�'
2060+
- Substitutes list and map insert values with null
2061+
- Maintains structure of child containers
2062+
- Replaces text mark values with null
2063+
- Preserves map keys and text annotation keys
2064+
2065+
**Important**: Your application needs to ensure that all peers receive the redacted version, otherwise the original document with sensitive information will still exist on other peers.
2066+
2067+
</details>
2068+
2069+
---
2070+
2071+
##### Use shallow snapshots to completely remove old history
2072+
2073+
When you need to completely remove ALL history older than a certain version point, shallow snapshots provide the solution.
2074+
2075+
<details>
2076+
<summary>How to remove old history with shallow snapshots</summary>
2077+
2078+
Shallow snapshots create a new document that preserves the current state but completely eliminates all history before a specified point, similar to Git's shallow clone functionality.
2079+
2080+
```typescript
2081+
const doc = new LoroDoc();
2082+
doc.setPeerId("1");
2083+
2084+
// Old history - will be completely removed
2085+
const text = doc.getText("text");
2086+
text.insert(0, "This document has a long history with many edits");
2087+
doc.commit();
2088+
text.insert(0, "Including some potentially sensitive information. ");
2089+
doc.commit();
2090+
2091+
// More recent history - will be preserved
2092+
text.delete(11, 55); // Remove the middle part
2093+
text.insert(11, "with sanitized history");
2094+
doc.commit();
2095+
2096+
// Create a sanitized version that removes ALL history before current point
2097+
const sanitizedSnapshot = doc.export({
2098+
mode: "shallow-snapshot",
2099+
frontiers: doc.oplogFrontiers()
2100+
});
2101+
2102+
// Create a new document from the sanitized snapshot
2103+
const sanitizedDoc = new LoroDoc();
2104+
sanitizedDoc.import(sanitizedSnapshot);
2105+
2106+
// The document has the final state
2107+
console.log(sanitizedDoc.getText("text").toString());
2108+
// Outputs: "Including with sanitized history"
2109+
2110+
// But ALL history before the snapshot point is completely removed
2111+
console.log(sanitizedDoc.isShallow()); // true
2112+
console.log(sanitizedDoc.shallowSinceFrontiers()); // Shows the starting point
2113+
```
2114+
2115+
This approach is useful for:
2116+
2117+
1. Completely removing all old history that might contain various sensitive information
2118+
2. Significantly reducing document size by eliminating unnecessary history
2119+
3. Creating clean document instances after certain milestones
2120+
4. Ensuring old operations cannot be recovered or examined
2121+
2122+
Compared to redaction:
2123+
- Shallow snapshots completely remove all operations before a version point
2124+
- Redaction selectively replaces just specific content with placeholders
2125+
2126+
**Important**: While both methods maintain future synchronization consistency, your application must distribute the sanitized document to all peers. Otherwise, the original document with sensitive information will still exist on other clients.
2127+
2128+
**When to use each approach**:
2129+
- Use **redaction** when you need to sanitize specific operations (like an accidental password paste) while preserving older history
2130+
- Use **shallow snapshots** when you want to completely eliminate all history before a certain point
2131+
2132+
</details>
2133+
2134+
---
2135+
2136+
##### You can store mappings between LoroDoc's peerIds and user IDs in the document itself
2137+
2138+
Use `doc.subscribeFirstCommitFromPeer(listener)` to associate peer information with user identities when a peer first interacts with the document.
2139+
2140+
<details>
2141+
<summary>How to track peer-to-user mappings</summary>
2142+
2143+
This functionality is essential for building user-centric features in collaborative applications. You often need bidirectional mapping between user IDs and peer IDs:
2144+
2145+
- **Finding all edits by a user**: When you need to retrieve all document edits made by a specific user ID, you must first find all peer IDs associated with that user
2146+
- **Showing edit attribution**: When displaying which user edited a piece of text, you need to map from the peer ID (stored in the operation) back to the user ID for display
2147+
2148+
This hook provides an ideal point to associate peer information (such as author identity) with the document. The listener is triggered on the first commit from each peer, allowing you to store user metadata within the document itself.
2149+
2150+
```typescript
2151+
const doc = new LoroDoc();
2152+
doc.setPeerId(0);
2153+
doc.subscribeFirstCommitFromPeer((e) => {
2154+
doc.getMap("users").set(e.peer, "user-" + e.peer);
2155+
});
2156+
doc.getList("list").insert(0, 100);
2157+
doc.commit();
2158+
expect(doc.getMap("users").get("0")).toBe("user-0");
2159+
```
2160+
2161+
This approach allows you to:
2162+
2163+
1. Automatically track which peers have contributed to the document
2164+
2. Store user metadata (names, emails, etc.) alongside the document
2165+
3. Build features like author attribution, presence indicators, or edit history
2166+
2167+
The mapping is stored within the document, so it automatically synchronizes across all peers and persists with the document's state.
2168+
2169+
</details>
2170+
2171+
---
2172+
2173+
##### You can use https://loro.dev/llms-full.txt to prompt your AI
2174+
2175+
When working with AI assistants or language models on Loro-related tasks, you can use these URLs to provide comprehensive context about Loro's capabilities and API:
2176+
2177+
- `https://loro.dev/llms-full.txt` - All the documentation in one file
2178+
- `https://loro.dev/llms.txt` - An overview of Loro website
2179+
19912180

19922181
# FILE: pages/docs/tutorial/map.md
19932182

@@ -2602,6 +2791,107 @@ of operations will converge to the same document state, obviating concerns about
26022791
the order, duplication, or timing of operation delivery.
26032792

26042793

2794+
# FILE: pages/docs/tutorial/ephemeral.md
2795+
2796+
---
2797+
keywords: "ephemeral, awareness, presence, collaborative"
2798+
description: "How to use Loro's ephemeral store feature to implement user awareness and online status management in real-time collaboration."
2799+
---
2800+
2801+
# Ephemeral Store
2802+
2803+
In real-time collaborative scenarios, Presence information is just as important as maintaining document consistency across peers through CRDTs. This includes information such as the current collaborator's username, mouse pointer position, or selected objects. We need a mechanism that doesn't persist in the CRDT Document but remains ephemeral, allowing collaborators to perceive each other's presence for better coordination and to avoid conflicts when multiple users edit the same object. This is why we've introduced the Ephemeral Store.
2804+
2805+
![](/images/ephemeral.png)
2806+
2807+
Since Ephemeral information is primarily used for real-time collaboration, we've chosen a simple yet effective approach. The Ephemeral Store is a timestamp-based, last-write-wins key-value store. Each entry maintains its own timestamp of the last update, enabling the system to send only the updated entry content rather than the complete current state.
2808+
2809+
2810+
## Example
2811+
2812+
```ts
2813+
import {
2814+
EphemeralStore,
2815+
EphemeralListener,
2816+
EphemeralStoreEvent,
2817+
} from "loro-crdt";
2818+
2819+
const store = new EphemeralStore();
2820+
// Set ephemeral data
2821+
store.set("loro-prosemirror", {
2822+
anchor: ...,
2823+
focus: ...,
2824+
user: "Alice"
2825+
});
2826+
store.set("online-users", ["Alice", "Bob"]);
2827+
2828+
expect(storeB.get("online-users")).toEqual(["Alice", "Bob"]);
2829+
// Encode only the data for `loro-prosemirror`
2830+
const encoded = store.encode("loro-prosemirror")
2831+
2832+
store.subscribe((e: EphemeralStoreEvent) => {
2833+
// Listen to changes from `local`, `remote`, or `timeout` events
2834+
});
2835+
```
2836+
2837+
## API
2838+
2839+
- `constructor(timeout)`:
2840+
Creates a new EphemeralStore instance with an optional timeout parameter (default: 30000ms). The timeout determines how long ephemeral data remains valid before being automatically removed.
2841+
2842+
- `set(key, value)`:
2843+
Sets a value for the specified key in the ephemeral store. If the key already exists, its value will be updated.
2844+
2845+
- `get(key)`:
2846+
Retrieves the current value for the specified key, or returns `undefined` if the key doesn't exist.
2847+
2848+
- `delete(key)`:
2849+
Removes the specified key and its associated value from the ephemeral store.
2850+
2851+
- `getAllStates()`:
2852+
Returns all current key-value pairs in the ephemeral store.
2853+
2854+
- `keys()`:
2855+
Returns an array of all keys currently in the ephemeral store.
2856+
2857+
- `encode(key)`:
2858+
Encodes the value associated with the specified key into a binary format that can be transmitted to other peers.
2859+
2860+
- `encodeAll()`:
2861+
Encodes all key-value pairs in the ephemeral store into a binary format.
2862+
2863+
- `apply(bytes)`:
2864+
Applies encoded ephemeral data received from other peers to the local ephemeral store.
2865+
2866+
- `subscribe((event: EphemeralStoreEvent)=>void)`:
2867+
Registers a listener function that will be called whenever the ephemeral store is updated, either from local changes, remote changes, or timeout events.
2868+
```ts
2869+
interface EphemeralStoreEvent {
2870+
// The source of the event: local changes, imported from remote, or timeout expiration
2871+
by: "local" | "import" | "timeout";
2872+
// Array of keys that were newly added
2873+
added: string[];
2874+
// Array of keys that had their values updated
2875+
updated: string[];
2876+
// Array of keys that were removed
2877+
removed: string[];
2878+
}
2879+
```
2880+
2881+
- `subscribeLocalUpdates((bytes: Uint8Array) => void)`:
2882+
Registers a listener that will be called only for local updates to the ephemeral store.
2883+
```ts
2884+
// you need maintain the Subscription to avoid gc
2885+
const _sub1 = ephemeral1.subscribeLocalUpdates((update) => {
2886+
ephemeral2.apply(update);
2887+
});
2888+
2889+
const _sub2 = ephemeral2.subscribeLocalUpdates((update) => {
2890+
ephemeral1.apply(update);
2891+
});
2892+
```
2893+
2894+
26052895
# FILE: pages/docs/tutorial/sync.md
26062896

26072897
## Sync
@@ -3892,6 +4182,68 @@ const sinceFrontiers = doc.shallowSinceFrontiers();
38924182

38934183
Note: A shallow document only contains history after a certain version point. Operations before the shallow start point are not included, but the document remains fully functional for collaboration.
38944184

4185+
### Redacting Sensitive Content
4186+
4187+
Loro allows you to redact specific segments of document history while preserving the rest. This is particularly useful when:
4188+
4189+
1. A user accidentally pastes sensitive information (like passwords or API keys) into the document
4190+
2. You need to remove just the sensitive part of the history while keeping older and newer edits intact
4191+
3. You want to share document history with sensitive segments sanitized
4192+
4193+
Here's how to use the redaction functionality:
4194+
4195+
```typescript
4196+
const doc = new LoroDoc();
4197+
doc.setPeerId("1");
4198+
4199+
// Create some content to be redacted
4200+
const text = doc.getText("text");
4201+
text.insert(0, "Sensitive information");
4202+
doc.commit();
4203+
4204+
const map = doc.getMap("map");
4205+
map.set("password", "secret123");
4206+
map.set("public", "public information");
4207+
doc.commit();
4208+
4209+
// Export JSON updates
4210+
const jsonUpdates = doc.exportJsonUpdates();
4211+
4212+
// Define version range to redact (redact the text content)
4213+
const versionRange = {
4214+
"1": [0, 21] // Redact the "Sensitive information"
4215+
};
4216+
4217+
// Apply redaction
4218+
const redactedJson = redactJsonUpdates(jsonUpdates, versionRange);
4219+
4220+
// Create a new document with redacted content
4221+
const redactedDoc = new LoroDoc();
4222+
redactedDoc.importJsonUpdates(redactedJson);
4223+
4224+
// The text content is now redacted with replacement characters
4225+
console.log(redactedDoc.getText("text").toString());
4226+
// Outputs: "���������������������"
4227+
4228+
// Map operations after the redacted range remain intact
4229+
console.log(redactedDoc.getMap("map").get("password")); // "secret123"
4230+
console.log(redactedDoc.getMap("map").get("public")); // "public information"
4231+
```
4232+
4233+
Redaction applies these rules to preserve document structure while removing sensitive content:
4234+
- Preserves delete and move operations
4235+
- Replaces text insertion content with Unicode replacement characters '�'
4236+
- Substitutes list and map insert values with null
4237+
- Maintains structure of nested containers
4238+
- Replaces text mark values with null
4239+
- Preserves map keys and text annotation keys
4240+
4241+
Note that redaction doesn't remove the operations completely - it just replaces the sensitive content with placeholders. If you need to completely remove portions of history, see the section on shallow snapshots in the [Tips](./tips.md) section.
4242+
4243+
#### Important: Synchronization Considerations
4244+
4245+
Both redaction and shallow snapshots maintain future synchronization consistency, but your application is responsible for ensuring all peers get the sanitized version. Otherwise, old instances of the document with sensitive information will still exist on other peers.
4246+
38954247
## Event Subscription
38964248

38974249
Subscribe to changes in the document:

public/llms.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Loro provides a robust framework for building collaborative applications with fe
1919
- [Tree](https://loro.dev/docs/tutorial/tree): Hierarchical data structures with Loro's movable tree CRDT
2020
- [Time Travel](https://loro.dev/docs/tutorial/time_travel): Implementing version control and history navigation
2121
- [Sync](https://loro.dev/docs/tutorial/sync): Guide to synchronizing data between instances
22+
- [Tips](https://loro.dev/docs/tutorial/tips): Tips and tricks for using Loro
2223

2324
## Advanced Topics
2425

0 commit comments

Comments
 (0)