Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
dist
# Ignore TS incremental build info everywhere in the repo
*.tsbuildinfo
.DS_Store
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pnpm add loro-mirror loro-crdt

```typescript
import { LoroDoc } from "loro-crdt";
import { schema, createStore } from "loro-mirror";
import { Mirror, schema } from "loro-mirror";

// Define your schema
const todoSchema = schema({
Expand All @@ -51,16 +51,16 @@ const todoSchema = schema({

// Create a Loro document
const doc = new LoroDoc();
// Create a store
const store = createStore({
// Create a mirror
const mirror = new Mirror({
doc,
schema: todoSchema,
initialState: { todos: [] },
});

// Update the state (immutable update)
// Note: setState is async; await it in non-React code
await store.setState((s) => ({
await mirror.setState((s) => ({
...s,
todos: [
...s.todos,
Expand All @@ -72,7 +72,7 @@ await store.setState((s) => ({
}));

// Or: draft-style updates (mutate a draft)
await store.setState((state) => {
await mirror.setState((state) => {
state.todos.push({
text: "Learn Loro Mirror",
completed: false,
Expand All @@ -82,7 +82,7 @@ await store.setState((state) => {
});

// Subscribe to state changes
store.subscribe((state) => {
mirror.subscribe((state) => {
console.log("State updated:", state);
});
```
Expand Down
64 changes: 7 additions & 57 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Contents

- Installation & Imports
- Core: Mirror and Store
- Core: Mirror
- Schema Builder
- Validation & Defaults
- Utilities (Advanced)
Expand All @@ -15,11 +15,11 @@
## Installation & Imports

- Install: `npm install loro-mirror loro-crdt`
- Import styles:
- Named imports (recommended): `import { Mirror, createStore, schema } from "loro-mirror"`
- Import styles:
- Named imports (recommended): `import { Mirror, schema } from "loro-mirror"`
- Default (convenience bundle of `schema` + `core`): `import loroMirror from "loro-mirror"`

## Core: Mirror and Store
## Core: Mirror

### Mirror

Expand All @@ -31,8 +31,8 @@
- `validateUpdates?: boolean` (default `true`) — validate on `setState`
- `throwOnValidationError?: boolean` (default `false`) — throw on schema validation errors
- `debug?: boolean` — verbose logging to console for diagnostics
- `checkStateConsistency?: boolean` (default `false`) — deep checks in-memory state equals normalized `doc` JSON after `setState`
- `inferOptions?: { defaultLoroText?: boolean; defaultMovableList?: boolean }` — inference hints when no schema covers a field
- `checkStateConsistency?: boolean` (default `false`) — deep checks in-memory state equals normalized `doc` JSON after `setState`
- `inferOptions?: { defaultLoroText?: boolean; defaultMovableList?: boolean }` — inference hints when no schema covers a field

- Methods
- `getState(): InferType<S>` — returns the current mirror state (immutable snapshot)
Expand Down Expand Up @@ -87,56 +87,6 @@
unsub();
```

### Store

Convenience wrapper around `Mirror` with a minimal Redux‑like surface.

- `createStore(options): Store<S>`
- `options: CreateStoreOptions<S>`
- `doc: LoroDoc`
- `schema: S`
- `initialState?: Partial<InferInputType<S>>`
- `validateUpdates?: boolean`
- `throwOnValidationError?: boolean` (default `true`)
- `debug?: boolean`
- `checkStateConsistency?: boolean`
- Returns `Store<S>` with:
- `getState(): InferType<S>`
- `setState(updater): Promise<void>` — same updater shapes as Mirror; await in non-React code
- `subscribe(cb): () => void` (same metadata as Mirror)
- `getMirror(): Mirror<S>`
- `getLoro(): LoroDoc`

- `createReducer(handlers) -> (store) => dispatch(type, payload)`
- Define an object of handlers that mutate an Immer draft. The returned `dispatch` wires those actions to `store.setState`.

Example

```ts
import { createStore, createReducer, schema } from "loro-mirror";
import { LoroDoc } from "loro-crdt";

const s = schema({
todos: schema.LoroList(schema.LoroMap({ id: schema.String(), text: schema.String(), done: schema.Boolean({ defaultValue: false }) }), (t) => t.id),
});

const store = createStore({ doc: new LoroDoc(), schema: s });

const actions = {
add(state, { id, text }: { id: string; text: string }) {
state.todos.push({ id, text });
},
toggle(state, id: string) {
const item = state.todos.find((t) => t.id === id);
if (item) item.done = !item.done;
},
};

const dispatch = createReducer(actions)(store);

dispatch("add", { id: "a", text: "Task" });
dispatch("toggle", "a");
```

## Schema Builder

Expand Down Expand Up @@ -278,4 +228,4 @@

---

Questions or gaps? If you need deeper internals (diff pipelines, event application), explore the source under `src/core/` — but for most apps, `Mirror`, the schema builders, and `createStore` are all you need.
Questions or gaps? If you need deeper internals (diff pipelines, event application), explore the source under `src/core/` — but for most apps, `Mirror` and the schema builders are all you need.
21 changes: 5 additions & 16 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

## Quick Start

Define a schema and wire a store to a `LoroDoc`.
Define a schema and instantiate a `Mirror` with a `LoroDoc`.

```ts
import { createStore, schema } from "loro-mirror";
import { Mirror, schema } from "loro-mirror";
import { LoroDoc } from "loro-crdt";

const doc = new LoroDoc();
Expand All @@ -27,7 +27,7 @@ const appSchema = schema({
notes: schema.LoroText(),
});

const store = createStore({ doc, schema: appSchema });
const store = new Mirror({ doc, schema: appSchema });

// Read state
const state = store.getState();
Expand All @@ -53,8 +53,6 @@ const unsubscribe = store.subscribe((next, { direction }) => {
});
```

Tip: If you need direct access, `store.getMirror()` returns the underlying `Mirror`.

## Installation

```bash
Expand Down Expand Up @@ -92,15 +90,6 @@ Trees are advanced usage; see Advanced: Trees at the end.

Types: `SyncDirection`, `UpdateMetadata`, `SetStateOptions`.

### Store

- `createStore({ doc, schema, initialState?, validateUpdates?=true, throwOnValidationError?=true, debug?=false })`
- Returns: `{ getState, setState, subscribe, getMirror, getLoro }`

### Reducer Helper

- `createReducer(handlers) -> (store) => dispatch(type, payload)`
- Handlers get an Immer draft; call `dispatch("addTodo", { text })` etc.

### Schema Builder

Expand Down Expand Up @@ -143,9 +132,9 @@ Trees are for hierarchical data where each node has a `data` map. The state shap
```ts
const node = schema.LoroMap({ name: schema.String({ required: true }) });
const s = schema({ tree: schema.LoroTree(node) });
const store = createStore({ doc: new LoroDoc(), schema: s });
const mirror = new Mirror({ doc: new LoroDoc(), schema: s });

await store.setState((st) => {
await mirror.setState((st) => {
st.tree.push({ data: { name: "root" }, children: [] });
});
```
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
* Core mirroring functionality for syncing application state with Loro CRDT
*/

export * from "./mirror";
export * from "./state";
export * from "./utils";
export { Mirror, SyncDirection, toNormalizedJson } from "./mirror";
export type {
InferContainerOptions,
MirrorOptions,
SetStateOptions,
SubscriberCallback,
UpdateMetadata,
} from "./mirror";
149 changes: 0 additions & 149 deletions packages/core/src/core/state.ts

This file was deleted.

12 changes: 10 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@

// Re-export all public APIs
export * from "./schema";
export * from "./core";
export * from "./constants";
export {
Mirror,
toNormalizedJson,
type MirrorOptions,
type SetStateOptions,
type UpdateMetadata,
type SubscriberCallback,
type InferContainerOptions,
SyncDirection,
} from "./core";

// Default export
import * as schema from "./schema";
Expand Down
Loading