Skip to content

Commit c2fd182

Browse files
committed
docs: refine docs
1 parent e3f1912 commit c2fd182

File tree

5 files changed

+111
-82
lines changed

5 files changed

+111
-82
lines changed

README.md

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,15 @@ import { schema, createStore } from "loro-mirror";
4040
// Define your schema
4141
const todoSchema = schema({
4242
todos: schema.LoroList(
43-
schema.LoroMap({
44-
id: schema.String(),
45-
text: schema.String(),
46-
completed: schema.Boolean({ defaultValue: false }),
47-
}),
43+
schema.LoroMap(
44+
{
45+
text: schema.String(),
46+
completed: schema.Boolean({ defaultValue: false }),
47+
},
48+
{ withCid: true },
49+
),
50+
// Use `$cid` (reuses Loro container id; explained later)
51+
(t) => t.$cid,
4852
),
4953
});
5054

@@ -63,7 +67,6 @@ store.setState((s) => ({
6367
todos: [
6468
...s.todos,
6569
{
66-
id: Date.now().toString(),
6770
text: "Learn Loro Mirror",
6871
completed: false,
6972
},
@@ -73,11 +76,11 @@ store.setState((s) => ({
7376
// Or: draft-style updates (mutate a draft)
7477
store.setState((state) => {
7578
state.todos.push({
76-
id: Date.now().toString(),
7779
text: "Learn Loro Mirror",
7880
completed: false,
7981
});
80-
// no return needed
82+
// `$cid` is injected automatically for withCid maps
83+
// and reuses the underlying Loro container id (explained later)
8184
});
8285

8386
// Subscribe to state changes
@@ -132,23 +135,25 @@ import { schema } from "loro-mirror";
132135

133136
type UserId = string & { __brand: "userId" };
134137
const appSchema = schema({
135-
user: schema.LoroMap({
136-
id: schema.String<UserId>(),
137-
name: schema.String(),
138-
age: schema.Number({ required: false }),
139-
}),
138+
user: schema.LoroMap(
139+
{
140+
name: schema.String(),
141+
age: schema.Number({ required: false }),
142+
},
143+
{ withCid: true },
144+
),
140145
tags: schema.LoroList(schema.String()),
141146
});
142147

143148
// Inferred state type:
144149
// type AppState = {
145-
// user: { id: UserId; name: string; age: number | undefined };
150+
// user: { $cid: string; name: string; age: number | undefined };
146151
// tags: string[];
147152
// }
148153
type AppState = InferType<typeof appSchema>;
149154
```
150155

151-
> **Note**: If you need optional custom string types like `{ id?: UserId }`, you currently need to explicitly define it as `schema.String<UserId>({ required: false })`
156+
> **Note**: If you need optional custom string types with generics (e.g., `{ status?: Status }`), explicitly define them as `schema.String<Status>({ required: false })`.
152157
153158
For `LoroMap` with dynamic key-value pairs:
154159

@@ -206,12 +211,14 @@ const result = validateSchema(appSchema, {
206211
```ts
207212
const todoSchema = schema({
208213
todos: schema.LoroMovableList(
209-
schema.LoroMap({
210-
id: schema.String(),
211-
text: schema.String(),
212-
completed: schema.Boolean({ defaultValue: false }),
213-
}),
214-
(t) => t.id,
214+
schema.LoroMap(
215+
{
216+
text: schema.String(),
217+
completed: schema.Boolean({ defaultValue: false }),
218+
},
219+
{ withCid: true },
220+
),
221+
(t) => t.$cid, // stable id from Loro container id ($cid)
215222
),
216223
});
217224
```
@@ -232,14 +239,17 @@ import { LoroDoc } from "loro-crdt";
232239
import { schema } from "loro-mirror";
233240
import { createLoroContext } from "loro-mirror-react";
234241

235-
// Define your schema
242+
// Define your schema (use `$cid` from withCid maps)
236243
const todoSchema = schema({
237244
todos: schema.LoroList(
238-
schema.LoroMap({
239-
id: schema.String({ required: true }),
240-
text: schema.String({ required: true }),
241-
completed: schema.Boolean({ defaultValue: false }),
242-
}),
245+
schema.LoroMap(
246+
{
247+
text: schema.String({ required: true }),
248+
completed: schema.Boolean({ defaultValue: false }),
249+
},
250+
{ withCid: true },
251+
),
252+
(t) => t.$cid, // uses Loro container id; see "$cid" section below
243253
),
244254
});
245255

@@ -262,19 +272,19 @@ function App() {
262272
// Todo list component
263273
function TodoList() {
264274
const todos = useLoroSelector((state) => state.todos);
265-
const toggleTodo = useLoroAction((s, id: string) => {
266-
const i = s.todos.findIndex((t) => t.id === id);
275+
const toggleTodo = useLoroAction((s, cid: string) => {
276+
const i = s.todos.findIndex((t) => t.$cid === cid);
267277
if (i !== -1) s.todos[i].completed = !s.todos[i].completed;
268278
}, []);
269279

270280
return (
271281
<ul>
272282
{todos.map((todo) => (
273-
<li key={todo.id}>
283+
<li key={todo.$cid}>
274284
<input
275285
type="checkbox"
276286
checked={todo.completed}
277-
onChange={() => toggleTodo(todo.id)}
287+
onChange={() => toggleTodo(todo.$cid)} // `$cid` is the Loro container id
278288
/>
279289
<span>{todo.text}</span>
280290
</li>
@@ -290,7 +300,6 @@ function AddTodoForm() {
290300
const addTodo = useLoroAction(
291301
(state) => {
292302
state.todos.push({
293-
id: Date.now().toString(),
294303
text: text.trim(),
295304
completed: false,
296305
});
@@ -394,12 +403,14 @@ import { Mirror, schema, SyncDirection } from "loro-mirror";
394403

395404
const todoSchema = schema({
396405
todos: schema.LoroList(
397-
schema.LoroMap({
398-
id: schema.String({ required: true }),
399-
text: schema.String({ required: true }),
400-
completed: schema.Boolean({ defaultValue: false }),
401-
}),
402-
(t) => t.id,
406+
schema.LoroMap(
407+
{
408+
text: schema.String({ required: true }),
409+
completed: schema.Boolean({ defaultValue: false }),
410+
},
411+
{ withCid: true },
412+
),
413+
(t) => t.$cid, // stable id from Loro container id ($cid)
403414
),
404415
});
405416

@@ -419,14 +430,19 @@ const unsubscribe = mirror.subscribe((state, { direction, tags }) => {
419430
mirror.setState(
420431
(s) => {
421432
s.todos.push({
422-
id: Date.now().toString(),
423433
text: "Write docs",
424434
completed: false,
425435
});
426436
},
427437
{ tags: ["ui:add"] },
428438
);
429439

440+
### How `$cid` Works
441+
442+
- Every Loro container has a stable container ID provided by Loro (e.g., a maps `container.id`).
443+
- When you enable `withCid: true` on `schema.LoroMap(...)`, Mirror injects a read-only `$cid` field into the mirrored state that equals the underlying Loro container ID.
444+
- `$cid` lives only in the app state and is never written back to the document. Mirror uses it for efficient diffs; you can use it as a stable list selector: `schema.LoroList(item, (x) => x.$cid)`.
445+
430446
// Cleanup
431447
unsubscribe();
432448
mirror.dispose();

packages/core/README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ const appSchema = schema({
1616
title: schema.String({ defaultValue: "Docs" }),
1717
darkMode: schema.Boolean({ defaultValue: false }),
1818
}),
19-
// LoroList: array of items (ID selector optional but recommended)
19+
// LoroList: array of items (use `$cid` from withCid maps)
2020
todos: schema.LoroList(
21-
schema.LoroMap({
22-
id: schema.String({ required: true }),
23-
text: schema.String(),
24-
}),
25-
(t) => t.id,
21+
schema.LoroMap(
22+
{
23+
text: schema.String(),
24+
},
25+
{ withCid: true },
26+
),
27+
(t) => t.$cid, // `$cid` reuses Loro container id (explained later)
2628
),
2729
// LoroText: collaborative text (string in state)
2830
notes: schema.LoroText(),
@@ -37,13 +39,13 @@ const state = store.getState();
3739
store.setState({
3840
...state,
3941
settings: { ...state.settings, darkMode: true },
40-
todos: [...state.todos, { id: "a", text: "Add milk" }],
42+
todos: [...state.todos, { text: "Add milk" }],
4143
notes: "Hello, team!",
4244
});
4345

4446
// Or mutate a draft (Immer-style)
4547
store.setState((s) => {
46-
s.todos.push({ id: "b", text: "Ship" });
48+
s.todos.push({ text: "Ship" });
4749
s.settings.title = "Project";
4850
});
4951

@@ -163,8 +165,8 @@ import { LoroDoc } from "loro-crdt";
163165

164166
const todosSchema = schema({
165167
todos: schema.LoroList(
166-
schema.LoroMap({ id: schema.String(), text: schema.String() }),
167-
(t) => t.id,
168+
schema.LoroMap({ text: schema.String() }, { withCid: true }),
169+
(t) => t.$cid, // list selector uses `$cid` (Loro container id)
168170
),
169171
});
170172

@@ -178,15 +180,15 @@ export function App() {
178180
<button
179181
onClick={() =>
180182
setState((s) => {
181-
s.todos.push({ id: crypto.randomUUID(), text: "New" });
183+
s.todos.push({ text: "New" });
182184
})
183185
}
184186
>
185187
Add
186188
</button>
187189
<ul>
188190
{state.todos.map((t) => (
189-
<li key={t.id}>{t.text}</li>
191+
<li key={t.$cid /* stable key from Loro container id */}>{t.text}</li>
190192
))}
191193
</ul>
192194
</div>

packages/core/src/core/loroEventApply.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ function applyTreeDiff(
489489
};
490490
if (treeId && nodeDataWithCid?.(treeId)) {
491491
const cid = getNodeDataCid?.(treeId, d.target);
492-
if (cid) node.data[CID_KEY] = cid;
492+
if (cid) node.data.$cid = cid;
493493
}
494494
const idx = clampIndex(d.index, arr.length + 1);
495495
arr.splice(idx, 0, node);

packages/jotai/README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@ type TodoStatus = "todo" | "inProgress" | "done";
3030
// 1. Define your schema
3131
const todoSchema = schema({
3232
todos: schema.LoroList(
33-
schema.LoroMap({
34-
text: schema.String(),
35-
status: schema.String<TodoStatus>()
36-
}),
33+
schema.LoroMap(
34+
{
35+
text: schema.String(),
36+
status: schema.String<TodoStatus>()
37+
},
38+
{ withCid: true },
39+
),
40+
(t) => t.$cid, // stable id from Loro container id
3741
),
3842
});
3943

@@ -68,7 +72,7 @@ function TodoApp() {
6872
<button onClick={addTodo}>Add Todo</button>
6973
<ul>
7074
{state.todos.map((todo) => (
71-
<li key={todo.text}>
75+
<li key={todo.$cid /* stable key from Loro container id */}>
7276
{todo.text}: {todo.status}
7377
</li>
7478
))}
@@ -78,6 +82,11 @@ function TodoApp() {
7882
}
7983
```
8084

85+
### About `$cid`
86+
87+
- Enabling `withCid: true` on `schema.LoroMap(...)` injects a read-only `$cid` in the mirrored state, equal to the underlying Loro container id.
88+
- Use `$cid` as a stable list selector and React key: `schema.LoroList(item, x => x.$cid)` and `<li key={todo.$cid}>`.
89+
8190
## License
8291

8392
[MIT](./LICENSE)

0 commit comments

Comments
 (0)