@editneo/sync 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +152 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # @editneo/sync
2
+
3
+ The synchronization layer for EditNeo. This package provides a `SyncManager` class that bridges the Zustand editor store with [Yjs](https://yjs.dev/) CRDTs, giving you offline persistence through IndexedDB and real-time multi-user collaboration through WebSockets — with automatic conflict resolution.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @editneo/sync @editneo/core
9
+ ```
10
+
11
+ ## How It Works
12
+
13
+ The `SyncManager` creates a Yjs document and maps the editor's block structure into two Yjs shared types:
14
+
15
+ - A `Y.Map` for individual blocks (keyed by block ID)
16
+ - A `Y.Array` for the ordered list of root block IDs
17
+
18
+ Changes flow in both directions:
19
+
20
+ 1. **Local to remote:** When the user edits a block through the store, your code calls `syncBlock()` or `syncRoot()` to push the change into the Yjs document. The Yjs providers then propagate it to IndexedDB and to other connected clients.
21
+
22
+ 2. **Remote to local:** When a change arrives from another client (or from IndexedDB on page load), the `SyncManager`'s observers detect the Yjs mutation and update the Zustand store accordingly.
23
+
24
+ Because Yjs is a CRDT, conflicting edits from multiple users are merged automatically without a central server making decisions.
25
+
26
+ ## Usage
27
+
28
+ ### Basic (offline only)
29
+
30
+ ```typescript
31
+ import { SyncManager } from "@editneo/sync";
32
+
33
+ const sync = new SyncManager("my-document");
34
+ // Data is now persisted to IndexedDB under the key "editneo-document-my-document"
35
+ ```
36
+
37
+ ### With real-time collaboration
38
+
39
+ ```typescript
40
+ const sync = new SyncManager("my-document", {
41
+ url: "wss://your-yjs-server.com",
42
+ room: "my-document",
43
+ });
44
+ ```
45
+
46
+ The `url` should point to a [y-websocket](https://github.com/yjs/y-websocket) server. The `room` determines which document the client joins — clients in the same room share the same Yjs document.
47
+
48
+ ### Syncing local changes
49
+
50
+ After the store modifies a block, push the change to Yjs:
51
+
52
+ ```typescript
53
+ import { useEditorStore } from "@editneo/core";
54
+
55
+ // After updating a block in the store
56
+ const updatedBlock = useEditorStore.getState().blocks["block-123"];
57
+ sync.syncBlock(updatedBlock);
58
+
59
+ // After reordering root blocks
60
+ const rootBlocks = useEditorStore.getState().rootBlocks;
61
+ sync.syncRoot(rootBlocks);
62
+
63
+ // After deleting a block
64
+ sync.deleteBlock("block-123");
65
+ ```
66
+
67
+ These methods include basic deduplication: they compare the incoming data against what Yjs currently holds and skip the write if nothing changed. This prevents trivial feedback loops where a Yjs observer fires a store update, which then tries to write back to Yjs.
68
+
69
+ ### Cursor awareness
70
+
71
+ When a WebSocket provider is active, you can access the [Yjs Awareness](https://docs.yjs.dev/getting-started/adding-awareness) instance to share cursor positions between users:
72
+
73
+ ```typescript
74
+ const awareness = sync.awareness;
75
+
76
+ if (awareness) {
77
+ // Set local cursor state
78
+ awareness.setLocalStateField("cursor", {
79
+ blockId: "block-abc",
80
+ offset: 12,
81
+ name: "Alice",
82
+ color: "#3b82f6",
83
+ });
84
+
85
+ // Listen for remote cursor changes
86
+ awareness.on("change", () => {
87
+ const states = awareness.getStates();
88
+ // states is a Map<clientID, { cursor: { blockId, offset, name, color } }>
89
+ });
90
+ }
91
+ ```
92
+
93
+ The `CursorOverlay` component from `@editneo/react` consumes this awareness data automatically.
94
+
95
+ ### Cleanup
96
+
97
+ When the editor unmounts or the document changes, destroy the sync manager to close connections and free resources:
98
+
99
+ ```typescript
100
+ sync.destroy();
101
+ ```
102
+
103
+ This destroys the IndexedDB provider, the WebSocket provider (if any), and the underlying Yjs document.
104
+
105
+ ## API Reference
106
+
107
+ ### `new SyncManager(docId, syncConfig?)`
108
+
109
+ | Parameter | Type | Description |
110
+ | ------------ | ------------------------------- | --------------------------------------------------------------------- |
111
+ | `docId` | `string` | Unique document identifier. Used to namespace the IndexedDB database. |
112
+ | `syncConfig` | `{ url: string; room: string }` | Optional. WebSocket server URL and room name for real-time sync. |
113
+
114
+ ### Instance Properties
115
+
116
+ | Property | Type | Description |
117
+ | ------------ | -------------------------------- | -------------------------------------------------- |
118
+ | `doc` | `Y.Doc` | The underlying Yjs document |
119
+ | `yBlocks` | `Y.Map<any>` | Yjs map of all blocks |
120
+ | `yRoot` | `Y.Array<string>` | Yjs array of root block IDs |
121
+ | `provider` | `IndexeddbPersistence` | The IndexedDB persistence provider |
122
+ | `wsProvider` | `WebsocketProvider \| undefined` | The WebSocket provider, if configured |
123
+ | `awareness` | `Awareness \| undefined` | The awareness instance from the WebSocket provider |
124
+
125
+ ### Instance Methods
126
+
127
+ | Method | Signature | Description |
128
+ | ------------- | -------------------------------- | ----------------------------------------------- |
129
+ | `syncBlock` | `(block: NeoBlock) => void` | Pushes a block update to Yjs (with dedup check) |
130
+ | `syncRoot` | `(rootBlocks: string[]) => void` | Replaces the root block ordering in Yjs |
131
+ | `deleteBlock` | `(id: string) => void` | Removes a block from the Yjs map |
132
+ | `destroy` | `() => void` | Tears down all providers and the Yjs document |
133
+
134
+ ## Running a WebSocket Server
135
+
136
+ The simplest way to run a Yjs WebSocket server for development:
137
+
138
+ ```bash
139
+ npx y-websocket
140
+ ```
141
+
142
+ This starts a server on `ws://localhost:1234`. Point your `syncConfig.url` there:
143
+
144
+ ```typescript
145
+ new SyncManager("doc", { url: "ws://localhost:1234", room: "doc" });
146
+ ```
147
+
148
+ For production, see the [y-websocket documentation](https://github.com/yjs/y-websocket) for deployment options including authentication, scaling, and persistence.
149
+
150
+ ## License
151
+
152
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editneo/sync",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Yjs-based CRDT sync manager for EditNeo with offline and WebSocket support",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",