Skip to content

Commit b0c5596

Browse files
committed
docs: api
1 parent 6cda9a2 commit b0c5596

File tree

2 files changed

+85
-7
lines changed

2 files changed

+85
-7
lines changed

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,88 @@ For detailed documentation, see the README files in each package:
197197
- [Core Documentation](./packages/core/README.md)
198198
- [React Documentation](./packages/react/README.md)
199199

200+
## API Reference (Core Mirror)
201+
202+
### Mirror
203+
204+
- `new Mirror(options)`: Creates a bidirectional sync between app state and a `LoroDoc`.
205+
- **`doc`**: `LoroDoc` – required Loro document instance.
206+
- **`schema`**: root schema – optional but recommended for strong typing and validation.
207+
- **`initialState`**: partial state – merged with schema defaults and current doc JSON.
208+
- **`validateUpdates`**: boolean (default `true`) – validate new state against schema.
209+
- **`throwOnValidationError`**: boolean (default `false`) – throw on invalid updates.
210+
- **`debug`**: boolean (default `false`) – log diffs and applied changes.
211+
- **`inferOptions`**: `{ defaultLoroText?: boolean; defaultMovableList?: boolean }` – influence container-type inference when inserting containers from plain values.
212+
213+
- `getState(): State`: Returns the current in-memory state view.
214+
- `setState(updater, options?)`: Update state and sync to Loro.
215+
- **`updater`**: either a partial object to shallow-merge or a function that may mutate a draft (Immer-style) or return a new state object.
216+
- **`options`**: `{ tags?: string | string[] }` – arbitrary tags attached to this update; delivered to subscribers in metadata.
217+
- `subscribe(callback): () => void`: Subscribe to state changes. `callback` receives `(state, metadata)` where `metadata` includes:
218+
- **`direction`**: `SyncDirection``FROM_LORO` when changes came from the doc, `TO_LORO` when produced locally, `BIDIRECTIONAL` for manual/initial syncs.
219+
- **`tags`**: `string[] | undefined` – tags provided via `setState`.
220+
- `dispose()`: Unsubscribe internal listeners and clear subscribers.
221+
222+
#### Notes
223+
224+
- **Lists and IDs**: If your list schema provides an `idSelector`, list updates use minimal add/remove/update/move operations; otherwise index-based diffs are applied.
225+
- **Container inference**: When schema is missing/ambiguous for a field, the mirror infers container types from values. `inferOptions.defaultLoroText` makes strings become `LoroText`; `inferOptions.defaultMovableList` makes arrays become `LoroMovableList`.
226+
227+
### Types
228+
229+
- `SyncDirection`:
230+
- `FROM_LORO` – applied due to incoming `LoroDoc` changes
231+
- `TO_LORO` – applied due to local `setState`
232+
- `BIDIRECTIONAL` – initial/manual sync context
233+
- `UpdateMetadata`: `{ direction: SyncDirection; tags?: string[] }`
234+
- `SetStateOptions`: `{ tags?: string | string[] }`
235+
236+
### Example
237+
238+
```ts
239+
import { LoroDoc } from "loro-crdt";
240+
import { Mirror, schema, SyncDirection } from "loro-mirror";
241+
242+
const todoSchema = schema({
243+
todos: schema.LoroList(
244+
schema.LoroMap({
245+
id: schema.String({ required: true }),
246+
text: schema.String({ required: true }),
247+
completed: schema.Boolean({ defaultValue: false }),
248+
}),
249+
(t) => t.id,
250+
),
251+
});
252+
253+
const doc = new LoroDoc();
254+
const mirror = new Mirror({ doc, schema: todoSchema, validateUpdates: true });
255+
256+
// Subscribe with metadata
257+
const unsubscribe = mirror.subscribe((state, { direction, tags }) => {
258+
if (direction === SyncDirection.FROM_LORO) {
259+
console.log("Remote update", tags);
260+
} else {
261+
console.log("Local update", tags);
262+
}
263+
});
264+
265+
// Update with draft mutation + tags
266+
mirror.setState(
267+
(s) => {
268+
s.todos.push({
269+
id: Date.now().toString(),
270+
text: "Write docs",
271+
completed: false,
272+
});
273+
},
274+
{ tags: ["ui:add"] },
275+
);
276+
277+
// Cleanup
278+
unsubscribe();
279+
mirror.dispose();
280+
```
281+
200282
## License
201283

202284
MIT

packages/core/src/core/mirror.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,6 @@ export class Mirror<S extends SchemaType> {
188188
private containerRegistry: ContainerRegistry = new Map();
189189
private subscriptions: (() => void)[] = [];
190190

191-
getContainerIds(): ContainerID[] {
192-
return Array.from(this.containerRegistry.keys());
193-
}
194-
195191
/**
196192
* Creates a new Mirror instance
197193
*/
@@ -1315,9 +1311,9 @@ export class Mirror<S extends SchemaType> {
13151311
typeof updater === "function"
13161312
? produce<InferType<S>>(this.state, (draft) => {
13171313
// Allow updater to either mutate draft or return a new state
1318-
const maybeResult = (updater as (s: InferType<S>) => InferType<S> | void)(
1319-
draft as InferType<S>,
1320-
);
1314+
const maybeResult = (
1315+
updater as (s: InferType<S>) => InferType<S> | void
1316+
)(draft as InferType<S>);
13211317
if (maybeResult && maybeResult !== draft) {
13221318
// Replace if updater returned a new state object
13231319
// Immer interprets a return value as replacement

0 commit comments

Comments
 (0)