|
27 | 27 | - `options: MirrorOptions<S>` |
28 | 28 | - `doc: LoroDoc` — the Loro document to sync with |
29 | 29 | - `schema?: S` — root schema (enables validation, typed defaults) |
30 | | - - `initialState?: Partial<InferType<S>>` — shallow overlay onto doc snapshot and schema defaults (does not write to Loro) |
| 30 | + - `initialState?: Partial<InferInputType<S>>` — shallow overlay onto doc snapshot and schema defaults (does not write to Loro) |
31 | 31 | - `validateUpdates?: boolean` (default `true`) — validate on `setState` |
32 | 32 | - `throwOnValidationError?: boolean` (default `false`) — throw on schema validation errors |
33 | 33 | - `debug?: boolean` — verbose logging to console for diagnostics |
|
36 | 36 |
|
37 | 37 | - Methods |
38 | 38 | - `getState(): InferType<S>` — returns the current mirror state (immutable snapshot) |
39 | | - - `setState(updater, options?): void` |
40 | | - - `updater: ((draft: InferType<S>) => void | InferType<S>) | Partial<InferType<S>>` |
41 | | - - Mutate a draft (Immer-style), or return a new object, or pass a shallow partial |
| 39 | + - `setState(updater, options?): Promise<void>` |
| 40 | + - Promise-returning; await in non-React code for ordering/errors. |
| 41 | + - `updater` supports both styles: |
| 42 | + - Mutate a draft: `(draft: InferType<S>) => void` |
| 43 | + - Return a new object: `(prev: Readonly<InferInputType<S>>) => InferInputType<S>` |
| 44 | + - Shallow partial: `Partial<InferInputType<S>>` |
42 | 45 | - `options?: { tags?: string | string[] }` — tags surface in subscriber metadata |
43 | 46 | - `subscribe((state, metadata) => void): () => void` |
44 | 47 | - `metadata: { direction: SyncDirection; tags?: string[] }` |
|
53 | 56 | - Mirror ignores events with origin `"to-loro"` to prevent feedback loops. |
54 | 57 | - Initial state precedence: defaults (from schema) → `doc` snapshot (normalized) → hinted shapes from `initialState` (no writes to Loro). |
55 | 58 | - Trees: mirror state uses `{ id: string; data: object; children: Node[] }`. Loro tree `meta` is normalized to `data`. |
56 | | - - `$cid` injection: when a map schema uses `{ withCid: true }`, mirror injects a read‑only `$cid` field in state equals to the Loro container ID. This is not written back to Loro and ignored by diffs. |
| 59 | + - `$cid` on maps: Mirror injects a read‑only `$cid` field into every LoroMap shape in state. It equals the Loro container ID, is not written back to Loro, and is ignored by diffs. |
57 | 60 | - Inference: with no schema, Mirror can infer containers from values; configure via `inferOptions`. |
58 | 61 |
|
59 | 62 | - Example |
|
72 | 75 |
|
73 | 76 | const mirror = new Mirror({ doc: new LoroDoc(), schema: appSchema }); |
74 | 77 |
|
75 | | - mirror.setState((s) => { |
| 78 | + await mirror.setState((s) => { |
76 | 79 | s.settings.title = "Docs"; |
77 | 80 | s.todos.push({ id: "1", text: "Ship" }); |
78 | 81 | }); |
|
92 | 95 | - `options: CreateStoreOptions<S>` |
93 | 96 | - `doc: LoroDoc` |
94 | 97 | - `schema: S` |
95 | | - - `initialState?: Partial<InferType<S>>` |
| 98 | + - `initialState?: Partial<InferInputType<S>>` |
96 | 99 | - `validateUpdates?: boolean` |
97 | 100 | - `throwOnValidationError?: boolean` (default `true`) |
98 | 101 | - `debug?: boolean` |
99 | 102 | - `checkStateConsistency?: boolean` |
100 | 103 | - Returns `Store<S>` with: |
101 | 104 | - `getState(): InferType<S>` |
102 | | - - `setState(updater): void` (same shapes as Mirror) |
| 105 | + - `setState(updater): Promise<void>` — same updater shapes as Mirror; await in non-React code |
103 | 106 | - `subscribe(cb): () => void` (same metadata as Mirror) |
104 | 107 | - `getMirror(): Mirror<S>` |
105 | 108 | - `getLoro(): LoroDoc` |
|
150 | 153 | - `schema.Ignore<T = unknown>(options?)` — present in state, ignored for Loro diffs/validation |
151 | 154 |
|
152 | 155 | - Containers |
153 | | - - `schema.LoroMap(definition, options?)` |
154 | | - - Options: e.g. `{ withCid?: boolean }` |
| 156 | + - `schema.LoroMap(definition)` |
155 | 157 | - Returns an object with `.catchall(valueSchema)` to allow mixed fixed keys + dynamic keys |
156 | 158 | - `schema.LoroMapRecord(valueSchema, options?)` — dynamic record (all keys share `valueSchema`) |
157 | 159 | - `schema.LoroList(itemSchema, idSelector?, options?)` |
|
168 | 170 | - `validate?: (value) => boolean | string` — custom validator message when not true |
169 | 171 |
|
170 | 172 | - Type inference |
171 | | - - `InferType<S>` — turns a schema into a TypeScript type |
| 173 | + - `InferType<S>` — state type produced by a schema |
| 174 | + - `InferInputType<S>` — input type accepted by `setState` (map `$cid` optional) |
172 | 175 | - `InferSchemaType<T>` — infers the type of a map definition |
173 | 176 | - `InferTreeNodeType<M>` / `InferTreeNodeTypeWithCid<M>` — inferred node shapes for trees |
174 | | - - `$cid` is present in inferred types when `{ withCid: true }` is used on a map schema (including list items and tree `data` maps) |
| 177 | + - `InferInputTreeNodeType<M>` — input node shape for trees (node `data.$cid` optional) |
| 178 | + - `$cid` is present in inferred types for all map schemas (including list items and tree `data` maps) |
175 | 179 |
|
176 | 180 | Examples |
177 | 181 |
|
|
233 | 237 | - `isArrayLike(v): v is unknown[]` |
234 | 238 | - `isStringLike(v): v is string` |
235 | 239 | - `isStateAndSchemaOfType(values, stateGuard, schemaGuard)` — generic narrow helper |
| 240 | + - Schema guards |
| 241 | + - `isContainerSchema(schema?): schema is ContainerSchemaType` |
| 242 | + - `isRootSchemaType(schema): schema is RootSchemaType` |
| 243 | + - `isLoroMapSchema(schema): schema is LoroMapSchema` |
| 244 | + - `isLoroListSchema(schema): schema is LoroListSchema` |
| 245 | + - `isListLikeSchema(schema): schema is LoroListSchema | LoroMovableListSchema` |
| 246 | + - `isLoroMovableListSchema(schema): schema is LoroMovableListSchema` |
| 247 | + - `isLoroTextSchema(schema): schema is LoroTextSchemaType` |
| 248 | + - `isLoroTreeSchema(schema): schema is LoroTreeSchema` |
236 | 249 |
|
237 | 250 | - Change helpers (primarily internal) |
238 | 251 | - `insertChildToMap(containerId, key, value): Change` — produce a map change (container‑aware) |
|
244 | 257 | - `MirrorOptions<S>` — constructor options for `Mirror` |
245 | 258 | - `SetStateOptions` — `{ tags?: string | string[] }` |
246 | 259 | - `UpdateMetadata` — `{ direction: SyncDirection; tags?: string[] }` |
| 260 | + - `InferType<S>` — state shape produced by a schema (includes `$cid` on maps) |
| 261 | + - `InferInputType<S>` — input shape accepted by `setState` (like `InferType` but `$cid` is optional on maps) |
| 262 | + - `InferContainerOptions` — `{ defaultLoroText?: boolean; defaultMovableList?: boolean }` |
| 263 | + - `SubscriberCallback<T>` — `(state: T, metadata: UpdateMetadata) => void` |
247 | 264 | - Change types (advanced): `ChangeKinds`, `Change`, `MapChangeKinds`, `ListChangeKinds`, `MovableListChangeKinds`, `TreeChangeKinds`, `TextChangeKinds` |
248 | 265 | - Schema types: `SchemaType`, `ContainerSchemaType`, `RootSchemaType`, `LoroMapSchema`, `LoroListSchema`, `LoroMovableListSchema`, `LoroTextSchemaType`, `LoroTreeSchema`, `SchemaOptions`, … |
249 | | - - `CID_KEY` — the literal string `"$cid"` used by `withCid` maps in mirrored state |
| 266 | +- `CID_KEY` — the literal string `"$cid"` used by mirrored maps |
250 | 267 |
|
251 | 268 | ## Tips & Recipes |
252 | 269 |
|
253 | 270 | - Lists: always provide an `idSelector` if items have stable IDs — enables minimal add/update/move/delete instead of positional churn. Prefer `LoroMovableList` when reorder operations are common. |
254 | | - - `$cid` for IDs: Use `{ withCid: true }` on `schema.LoroMap(...)` to get a stable `$cid` you can use as a React `key` or as a `LoroList` item selector: `(item) => item.$cid`. |
255 | | - - `setState` styles: choose your favorite — draft mutation or returning a new object. Both are supported. |
| 271 | +- `$cid` for IDs: Every `LoroMap` includes a stable `$cid` you can use as a React `key` or as a `LoroList` item selector: `(item) => item.$cid`. |
| 272 | + - `setState` styles: choose your favorite — draft mutation or returning a new object. Both are supported and return a Promise. Await in non-React code when you need ordering or to catch validation errors. |
256 | 273 | - Tagging updates: pass `{ tags: ["analytics", "user"] }` to `setState` and inspect `metadata.tags` in subscribers. |
257 | 274 | - Trees: you can create/move/delete nodes in state (Mirror emits precise `tree-create/move/delete`). Node `data` is a normal Loro map — nested containers (text, list, map) update incrementally. |
258 | 275 | - Initial state: providing `initialState` hints shapes and defaults in memory, but does not write into the LoroDoc until a real change occurs. |
|
0 commit comments