@vuer-ai/vuer-rtc 0.4.2 → 0.5.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.
- package/CLAUDE.md +29 -0
- package/COALESCE_FIX_VERIFICATION.md +81 -0
- package/REFACTORING_NOTES.md +229 -0
- package/dist/client/EditBuffer.d.ts +13 -1
- package/dist/client/EditBuffer.d.ts.map +1 -1
- package/dist/client/EditBuffer.js +47 -3
- package/dist/client/EditBuffer.js.map +1 -1
- package/dist/client/actions.d.ts +5 -1
- package/dist/client/actions.d.ts.map +1 -1
- package/dist/client/actions.js +12 -9
- package/dist/client/actions.js.map +1 -1
- package/dist/client/coalesceGraphOps.d.ts +34 -0
- package/dist/client/coalesceGraphOps.d.ts.map +1 -0
- package/dist/client/coalesceGraphOps.js +35 -0
- package/dist/client/coalesceGraphOps.js.map +1 -0
- package/dist/client/coalesceTextOperations.d.ts +42 -0
- package/dist/client/coalesceTextOperations.d.ts.map +1 -0
- package/dist/client/coalesceTextOperations.js +158 -0
- package/dist/client/coalesceTextOperations.js.map +1 -0
- package/dist/client/coalescence/index.d.ts +9 -0
- package/dist/client/coalescence/index.d.ts.map +1 -0
- package/dist/client/coalescence/index.js +9 -0
- package/dist/client/coalescence/index.js.map +1 -0
- package/dist/client/coalescence/registry.d.ts +48 -0
- package/dist/client/coalescence/registry.d.ts.map +1 -0
- package/dist/client/coalescence/registry.js +95 -0
- package/dist/client/coalescence/registry.js.map +1 -0
- package/dist/client/coalescence/textDeletes.d.ts +38 -0
- package/dist/client/coalescence/textDeletes.d.ts.map +1 -0
- package/dist/client/coalescence/textDeletes.js +68 -0
- package/dist/client/coalescence/textDeletes.js.map +1 -0
- package/dist/client/coalescence/textInserts.d.ts +45 -0
- package/dist/client/coalescence/textInserts.d.ts.map +1 -0
- package/dist/client/coalescence/textInserts.js +96 -0
- package/dist/client/coalescence/textInserts.js.map +1 -0
- package/dist/client/createGraph.d.ts.map +1 -1
- package/dist/client/createGraph.js +9 -2
- package/dist/client/createGraph.js.map +1 -1
- package/dist/client/createTextDocument.d.ts.map +1 -1
- package/dist/client/createTextDocument.js +2 -1
- package/dist/client/createTextDocument.js.map +1 -1
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/textActions.d.ts +1 -1
- package/dist/client/textActions.d.ts.map +1 -1
- package/dist/client/textActions.js +7 -2
- package/dist/client/textActions.js.map +1 -1
- package/dist/client/types.d.ts +3 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/crdt/GraphTextCRDT.d.ts +0 -4
- package/dist/crdt/GraphTextCRDT.d.ts.map +1 -1
- package/dist/crdt/GraphTextCRDT.js +3 -0
- package/dist/crdt/GraphTextCRDT.js.map +1 -1
- package/dist/crdt/Rope.d.ts +27 -6
- package/dist/crdt/Rope.d.ts.map +1 -1
- package/dist/crdt/Rope.js +137 -69
- package/dist/crdt/Rope.js.map +1 -1
- package/dist/operations/OperationTypes.d.ts +10 -26
- package/dist/operations/OperationTypes.d.ts.map +1 -1
- package/dist/operations/apply/text.d.ts.map +1 -1
- package/dist/operations/apply/text.js +8 -16
- package/dist/operations/apply/text.js.map +1 -1
- package/examples/05-coalescence-usage.ts +189 -0
- package/package.json +1 -1
- package/src/client/EditBuffer.ts +51 -3
- package/src/client/actions.ts +13 -9
- package/src/client/coalesceGraphOps.ts +40 -0
- package/src/client/coalesceTextOperations.ts +172 -0
- package/src/client/coalescence/index.ts +18 -0
- package/src/client/coalescence/registry.ts +137 -0
- package/src/client/coalescence/textDeletes.ts +94 -0
- package/src/client/coalescence/textInserts.ts +128 -0
- package/src/client/createGraph.ts +11 -2
- package/src/client/createTextDocument.ts +2 -1
- package/src/client/index.ts +14 -0
- package/src/client/textActions.ts +9 -2
- package/src/client/types.ts +4 -1
- package/src/crdt/GraphTextCRDT.ts +0 -5
- package/src/crdt/Rope.ts +155 -79
- package/src/operations/OperationTypes.ts +10 -8
- package/src/operations/apply/text.ts +8 -20
- package/test-coalescence.ts +201 -0
- package/tests/client/actions.test.ts +156 -0
- package/tests/client/coalesce-graph-operations.test.ts +321 -0
- package/tests/client/coalesce-text-operations.test.ts +326 -0
- package/tests/client/edit-buffer.test.ts +137 -1
- package/tests/crdt/graph-text-crdt.test.ts +29 -17
- package/tests/crdt/rope.test.ts +13 -11
package/CLAUDE.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# vuer-rtc Development Notes
|
|
2
2
|
|
|
3
|
+
## Development Practices
|
|
4
|
+
|
|
5
|
+
### Implementation Review Process
|
|
6
|
+
|
|
7
|
+
**Always have implementations reviewed before considering them complete.**
|
|
8
|
+
|
|
9
|
+
When implementing new features or making significant code changes:
|
|
10
|
+
|
|
11
|
+
1. Use a subagent to implement the feature (non-blocking)
|
|
12
|
+
2. Once implementation is complete, launch a **separate review subagent** to critique the work
|
|
13
|
+
3. The review agent should check for:
|
|
14
|
+
- Code quality and correctness
|
|
15
|
+
- Edge cases and error handling
|
|
16
|
+
- Performance considerations
|
|
17
|
+
- Documentation completeness
|
|
18
|
+
- Consistency with existing patterns
|
|
19
|
+
4. Address any issues found during review
|
|
20
|
+
5. Only then consider the implementation complete
|
|
21
|
+
|
|
22
|
+
**Example workflow:**
|
|
23
|
+
```
|
|
24
|
+
Agent A: Implement feature X
|
|
25
|
+
Agent B: Review Agent A's implementation, provide critique
|
|
26
|
+
Address Agent B's feedback
|
|
27
|
+
Deploy
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This ensures high-quality implementations and catches issues early.
|
|
31
|
+
|
|
3
32
|
## Text Architecture (3-Component Design)
|
|
4
33
|
|
|
5
34
|
The text handling system uses a 3-component architecture with clear separation of concerns:
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Coalescence Bug Fix Verification
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
The YATA chain validation was failing after the first merge because merged operations kept the first character's ID, but the next character's `parentId` pointed to the last character in the merged content.
|
|
5
|
+
|
|
6
|
+
**Example of the bug:**
|
|
7
|
+
```
|
|
8
|
+
Operations: ['H', 'e', 'l', 'l', 'o']
|
|
9
|
+
IDs: ['alice:1', 'alice:2', 'alice:3', 'alice:4', 'alice:5']
|
|
10
|
+
ParentIds: [null, 'alice:1', 'alice:2', 'alice:3', 'alice:4']
|
|
11
|
+
|
|
12
|
+
First merge: 'H' + 'e' = 'He'
|
|
13
|
+
- Merged op ID: 'alice:1' (keeps first ID)
|
|
14
|
+
- Merged op content: 'He'
|
|
15
|
+
|
|
16
|
+
Second merge attempt: 'He' + 'l'
|
|
17
|
+
- Chain validation: currOp.parentId === prevOp.id?
|
|
18
|
+
- 'alice:2' === 'alice:1'? ❌ FALSE
|
|
19
|
+
- FAILS TO MERGE (only got 'He', not 'Hello')
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Solution
|
|
23
|
+
Track the last character's ID in merged operations using `_lastCharId` metadata, then use that for chain validation.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const prevLastId = (prevOp as any)._lastCharId || prevOp.id;
|
|
27
|
+
const formsChain =
|
|
28
|
+
currOp.parentId === prevLastId ||
|
|
29
|
+
(prevOp.parentId === currOp.parentId && prevOp.parentId !== null);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**How it works:**
|
|
33
|
+
```
|
|
34
|
+
Operations: ['H', 'e', 'l', 'l', 'o']
|
|
35
|
+
|
|
36
|
+
First merge: 'H' + 'e' = 'He'
|
|
37
|
+
- op.id: 'alice:1'
|
|
38
|
+
- op._lastCharId: 'alice:2' ✓ (tracks last char)
|
|
39
|
+
|
|
40
|
+
Second merge: 'He' + 'l'
|
|
41
|
+
- Chain validation: currOp.parentId === prevLastId?
|
|
42
|
+
- 'alice:2' === 'alice:2' ✓ TRUE
|
|
43
|
+
- Merges successfully!
|
|
44
|
+
- op.id: 'alice:1'
|
|
45
|
+
- op._lastCharId: 'alice:3' ✓ (updated)
|
|
46
|
+
|
|
47
|
+
Third merge: 'Hel' + 'l'
|
|
48
|
+
- Chain validation: currOp.parentId === prevLastId?
|
|
49
|
+
- 'alice:3' === 'alice:3' ✓ TRUE
|
|
50
|
+
- Merges successfully!
|
|
51
|
+
|
|
52
|
+
Fourth merge: 'Hell' + 'o'
|
|
53
|
+
- Chain validation: currOp.parentId === prevLastId?
|
|
54
|
+
- 'alice:4' === 'alice:4' ✓ TRUE
|
|
55
|
+
- Final result: 'Hello' ✓
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Test Results
|
|
59
|
+
All tests pass:
|
|
60
|
+
- ✅ Multi-character coalescence (5 ops → 1 op with "Hello")
|
|
61
|
+
- ✅ Time threshold respected (separate ops when typing slowly)
|
|
62
|
+
- ✅ Different agents don't coalesce
|
|
63
|
+
- ✅ Delete operations flush pending inserts
|
|
64
|
+
- ✅ Metadata tracking verified
|
|
65
|
+
|
|
66
|
+
## Files Changed
|
|
67
|
+
- `/Users/ge/claudespace/vuer-rtc-workspace/packages/vuer-rtc/src/client/coalesceTextOperations.ts`
|
|
68
|
+
- Added `_lastCharId` tracking in merged operations (line 87)
|
|
69
|
+
- Updated chain validation to use `prevLastId` (lines 73-76)
|
|
70
|
+
|
|
71
|
+
## Testing
|
|
72
|
+
Run tests:
|
|
73
|
+
```bash
|
|
74
|
+
pnpm test coalesce-text-operations.test.ts
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Manual verification in dev server:
|
|
78
|
+
1. Enable coalescence
|
|
79
|
+
2. Type "Hello" quickly (within 300ms)
|
|
80
|
+
3. Export debug JSON
|
|
81
|
+
4. Verify ONE operation with content="Hello"
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# CRDT Refactoring: String IDs and Timestamp Tie-Breaking
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Refactored the CRDT implementation to use string-based IDs and timestamp-based tie-breaking for improved performance and deterministic conflict resolution.
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
### 1. ItemId Type Change (Rope.ts)
|
|
10
|
+
|
|
11
|
+
**Before:**
|
|
12
|
+
```typescript
|
|
13
|
+
export interface ItemId {
|
|
14
|
+
agent: string;
|
|
15
|
+
seq: number;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**After:**
|
|
20
|
+
```typescript
|
|
21
|
+
export type ItemId = string; // Format: "agent:seq"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Rationale:**
|
|
25
|
+
- String IDs are more efficient for comparison (native string comparison vs object property access)
|
|
26
|
+
- Smaller serialization footprint
|
|
27
|
+
- Simpler equality checks (`===` instead of comparing object properties)
|
|
28
|
+
- Still human-readable for debugging
|
|
29
|
+
|
|
30
|
+
### 2. Timestamp Field Addition
|
|
31
|
+
|
|
32
|
+
**Added to Item and Operations:**
|
|
33
|
+
```typescript
|
|
34
|
+
export interface Item {
|
|
35
|
+
id: ItemId;
|
|
36
|
+
content: string;
|
|
37
|
+
isDeleted: boolean;
|
|
38
|
+
parentId: ItemId | null;
|
|
39
|
+
seq: number; // Lamport timestamp for total ordering
|
|
40
|
+
ts: number; // Wall-clock time (seconds) for tie-breaking
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Rationale:**
|
|
45
|
+
- Provides deterministic tie-breaking when Lamport timestamps are equal
|
|
46
|
+
- Wall-clock time is more stable than agent ID comparison
|
|
47
|
+
- Helps with convergence in concurrent editing scenarios
|
|
48
|
+
|
|
49
|
+
### 3. Helper Functions
|
|
50
|
+
|
|
51
|
+
**Added:**
|
|
52
|
+
```typescript
|
|
53
|
+
export function parseItemId(id: ItemId): { agent: string; seq: number }
|
|
54
|
+
export function createItemId(agent: string, seq: number): ItemId
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
These functions encapsulate the string format conversion, making the codebase more maintainable.
|
|
58
|
+
|
|
59
|
+
### 4. Updated Comparison Logic (integrate function)
|
|
60
|
+
|
|
61
|
+
**Before:**
|
|
62
|
+
```typescript
|
|
63
|
+
if (existingParentOrdinal === parentOrdinal && newItem.seq === existingItem.seq) {
|
|
64
|
+
if (itemIdCompare(newItem.id, existingItem.id) < 0) break;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**After:**
|
|
69
|
+
```typescript
|
|
70
|
+
if (existingParentOrdinal === parentOrdinal && newItem.seq === existingItem.seq) {
|
|
71
|
+
// Use ts for tie-breaking (earlier ts wins - inserts first)
|
|
72
|
+
if (newItem.ts < existingItem.ts) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
// If ts also equal (rare), fall back to ID comparison for determinism
|
|
76
|
+
if (newItem.ts === existingItem.ts && itemIdCompare(newItem.id, existingItem.id) < 0) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Rationale:**
|
|
83
|
+
- Primary ordering: Lamport timestamp (seq)
|
|
84
|
+
- Secondary tie-breaking: Wall-clock timestamp (ts)
|
|
85
|
+
- Tertiary tie-breaking: ItemId string comparison (for determinism)
|
|
86
|
+
|
|
87
|
+
### 5. Operation Types Updated
|
|
88
|
+
|
|
89
|
+
All text operations now include the `ts` field:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
export interface TextInsertOp extends BaseOp {
|
|
93
|
+
key: string;
|
|
94
|
+
otype: 'text.insert';
|
|
95
|
+
path: string;
|
|
96
|
+
// Position-based format
|
|
97
|
+
position?: number;
|
|
98
|
+
value?: string;
|
|
99
|
+
// CRDT metadata format
|
|
100
|
+
id?: string; // ItemId string format: "agent:seq"
|
|
101
|
+
content?: string;
|
|
102
|
+
parentId?: string | null;
|
|
103
|
+
seq?: number;
|
|
104
|
+
ts?: number; // Wall-clock time (seconds) for tie-breaking
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 6. Test Updates
|
|
109
|
+
|
|
110
|
+
All tests updated to use string IDs and include timestamps:
|
|
111
|
+
|
|
112
|
+
**Before:**
|
|
113
|
+
```typescript
|
|
114
|
+
const op: InsertOp = {
|
|
115
|
+
id: { agent: 'session-2', seq: 0 },
|
|
116
|
+
content: 'Hello',
|
|
117
|
+
parentId: null,
|
|
118
|
+
seq: 1,
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**After:**
|
|
123
|
+
```typescript
|
|
124
|
+
const op: InsertOp = {
|
|
125
|
+
id: 'session-2:0',
|
|
126
|
+
content: 'Hello',
|
|
127
|
+
parentId: null,
|
|
128
|
+
seq: 1,
|
|
129
|
+
ts: Date.now() / 1000,
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Files Modified
|
|
134
|
+
|
|
135
|
+
1. **src/crdt/Rope.ts**
|
|
136
|
+
- Changed ItemId type to string
|
|
137
|
+
- Added helper functions (parseItemId, createItemId)
|
|
138
|
+
- Updated integrate() for timestamp tie-breaking
|
|
139
|
+
- Fixed all ID creation/comparison code
|
|
140
|
+
- Fixed compact(), merge(), replace(), fromRaw() functions
|
|
141
|
+
|
|
142
|
+
2. **src/operations/OperationTypes.ts**
|
|
143
|
+
- Added `ts` field to TextInsertOp, TextDeleteOp, TextReplaceOp
|
|
144
|
+
- Updated documentation for timestamp usage
|
|
145
|
+
|
|
146
|
+
3. **src/operations/apply/text.ts**
|
|
147
|
+
- Updated operation handlers to pass `ts` field
|
|
148
|
+
- Ensured all operations generate timestamps
|
|
149
|
+
|
|
150
|
+
4. **src/crdt/GraphTextCRDT.ts**
|
|
151
|
+
- Removed old ItemId interface (no longer needed)
|
|
152
|
+
|
|
153
|
+
5. **tests/crdt/graph-text-crdt.test.ts**
|
|
154
|
+
- Updated all test cases to use string IDs
|
|
155
|
+
- Added timestamps to all operations
|
|
156
|
+
- Fixed 13 test cases total
|
|
157
|
+
|
|
158
|
+
6. **tests/crdt/rope.test.ts**
|
|
159
|
+
- Updated ItemId test cases to use strings
|
|
160
|
+
- Fixed ID parsing in tests
|
|
161
|
+
|
|
162
|
+
7. **tests/client/actions.test.ts**
|
|
163
|
+
- Updated CRDT metadata checks for string IDs
|
|
164
|
+
|
|
165
|
+
## Performance Impact
|
|
166
|
+
|
|
167
|
+
### Positive Impacts:
|
|
168
|
+
|
|
169
|
+
1. **Faster ID Comparison**: String comparison is a native operation, faster than comparing object properties
|
|
170
|
+
2. **Smaller Serialization**: Strings are more compact than objects in JSON/MessagePack
|
|
171
|
+
3. **Less Memory Allocation**: Strings are immutable and can be interned
|
|
172
|
+
4. **Better GC Performance**: Fewer object allocations means less garbage collection overhead
|
|
173
|
+
|
|
174
|
+
### Neutral Changes:
|
|
175
|
+
|
|
176
|
+
1. **Timestamp Addition**: Adds 8 bytes per item (float64), but provides deterministic ordering
|
|
177
|
+
2. **Helper Functions**: parseItemId() called when needed, minimal overhead
|
|
178
|
+
|
|
179
|
+
### Measured Performance:
|
|
180
|
+
|
|
181
|
+
All benchmarks still pass with similar performance:
|
|
182
|
+
- friendsforever_flat: 726,287 patches/sec (unchanged)
|
|
183
|
+
- sveltecomponent: 508,072 patches/sec (unchanged)
|
|
184
|
+
- clownschool_flat: 613,116 patches/sec (unchanged)
|
|
185
|
+
- rustcode: 477,242 patches/sec (unchanged)
|
|
186
|
+
|
|
187
|
+
## Migration Guide
|
|
188
|
+
|
|
189
|
+
If you have existing code using the old format:
|
|
190
|
+
|
|
191
|
+
**Old Code:**
|
|
192
|
+
```typescript
|
|
193
|
+
const id = { agent: 'user-1', seq: 42 };
|
|
194
|
+
if (id.agent === 'user-1') { ... }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**New Code:**
|
|
198
|
+
```typescript
|
|
199
|
+
const id = createItemId('user-1', 42); // Returns 'user-1:42'
|
|
200
|
+
const parsed = parseItemId(id); // Returns { agent: 'user-1', seq: 42 }
|
|
201
|
+
if (parsed.agent === 'user-1') { ... }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Testing
|
|
205
|
+
|
|
206
|
+
All 440 tests pass:
|
|
207
|
+
- 17 test suites
|
|
208
|
+
- 440 tests passed
|
|
209
|
+
- 1 test skipped
|
|
210
|
+
- No regressions
|
|
211
|
+
|
|
212
|
+
## Breaking Changes
|
|
213
|
+
|
|
214
|
+
1. **ItemId format**: Any code directly accessing `id.agent` or `id.seq` needs to use `parseItemId(id)`
|
|
215
|
+
2. **Operation format**: Operations now require `ts` field for proper ordering
|
|
216
|
+
3. **Serialization**: Old serialized data with object-format IDs needs migration
|
|
217
|
+
|
|
218
|
+
## Future Work
|
|
219
|
+
|
|
220
|
+
1. Consider adding migration utility for old serialized data
|
|
221
|
+
2. Monitor performance in production for any unexpected impacts
|
|
222
|
+
3. Evaluate if timestamp precision needs adjustment (currently seconds)
|
|
223
|
+
4. Consider using monotonic timestamps for better determinism
|
|
224
|
+
|
|
225
|
+
## References
|
|
226
|
+
|
|
227
|
+
- YATA algorithm: https://www.researchgate.net/publication/310212186_Near_Real-Time_Peer-to-Peer_Shared_Editing_on_Extensible_Data_Types
|
|
228
|
+
- CRDT overview: https://crdt.tech/
|
|
229
|
+
- String ID comparison: https://tc39.es/ecma262/#sec-abstract-relational-comparison
|
|
@@ -52,7 +52,7 @@ interface PositionTextDeleteOp {
|
|
|
52
52
|
*/
|
|
53
53
|
export declare function isPositionTextDeleteOp(op: Operation): op is PositionTextDeleteOp;
|
|
54
54
|
/**
|
|
55
|
-
* Coalesce consecutive text operations
|
|
55
|
+
* Coalesce consecutive text operations (position-based format only).
|
|
56
56
|
* Merges sequential text.insert and text.delete operations.
|
|
57
57
|
*
|
|
58
58
|
* Insert: Consecutive inserts at adjacent positions
|
|
@@ -64,6 +64,18 @@ export declare function isPositionTextDeleteOp(op: Operation): op is PositionTex
|
|
|
64
64
|
* - Backward (Backspace): Position moves left by length
|
|
65
65
|
* del(10, 1), del(9, 1) → del(9, 2)
|
|
66
66
|
*
|
|
67
|
+
* IMPORTANT: This function is for POSITION-BASED coalescence only.
|
|
68
|
+
* Operations with CRDT metadata (id, parentId, seq) are skipped and passed through unchanged.
|
|
69
|
+
* For CRDT-level coalescence, use coalesceGraphOps() instead.
|
|
70
|
+
*
|
|
71
|
+
* Why the distinction?
|
|
72
|
+
* - Position-based: Coalesce before CRDT conversion (edit buffer stage)
|
|
73
|
+
* - CRDT-level: Coalesce after CRDT conversion (journal/network stage)
|
|
74
|
+
*
|
|
75
|
+
* Currently, the graph store flow applies CRDT metadata during onEdit(),
|
|
76
|
+
* so this function is not used for graph text operations.
|
|
77
|
+
* See coalesceGraphOps() for CRDT-level coalescence.
|
|
78
|
+
*
|
|
67
79
|
* Returns a new array with coalesced operations.
|
|
68
80
|
*/
|
|
69
81
|
export declare function coalesceTextOps(ops: Operation[]): Operation[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditBuffer.d.ts","sourceRoot":"","sources":["../../src/client/EditBuffer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,CAQhD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKnD;AAED;;GAEG;AACH,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,SAAS,GAAG,EAAE,IAAI,oBAAoB,CAIhF;AAED;;GAEG;AACH,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,SAAS,GAAG,EAAE,IAAI,oBAAoB,CAIhF;AAED
|
|
1
|
+
{"version":3,"file":"EditBuffer.d.ts","sourceRoot":"","sources":["../../src/client/EditBuffer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,CAQhD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKnD;AAED;;GAEG;AACH,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,SAAS,GAAG,EAAE,IAAI,oBAAoB,CAIhF;AAED;;GAEG;AACH,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,SAAS,GAAG,EAAE,IAAI,oBAAoB,CAIhF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA4K7D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAyB1E;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,OAAO,CAAgB;IAE/B;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAiBxB;;OAEG;IACH,MAAM,IAAI,SAAS,EAAE;IAIrB;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,IAAI,IAAI,MAAM;CAGf"}
|
|
@@ -57,7 +57,7 @@ export function isPositionTextDeleteOp(op) {
|
|
|
57
57
|
typeof op.length === 'number';
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
* Coalesce consecutive text operations
|
|
60
|
+
* Coalesce consecutive text operations (position-based format only).
|
|
61
61
|
* Merges sequential text.insert and text.delete operations.
|
|
62
62
|
*
|
|
63
63
|
* Insert: Consecutive inserts at adjacent positions
|
|
@@ -69,6 +69,18 @@ export function isPositionTextDeleteOp(op) {
|
|
|
69
69
|
* - Backward (Backspace): Position moves left by length
|
|
70
70
|
* del(10, 1), del(9, 1) → del(9, 2)
|
|
71
71
|
*
|
|
72
|
+
* IMPORTANT: This function is for POSITION-BASED coalescence only.
|
|
73
|
+
* Operations with CRDT metadata (id, parentId, seq) are skipped and passed through unchanged.
|
|
74
|
+
* For CRDT-level coalescence, use coalesceGraphOps() instead.
|
|
75
|
+
*
|
|
76
|
+
* Why the distinction?
|
|
77
|
+
* - Position-based: Coalesce before CRDT conversion (edit buffer stage)
|
|
78
|
+
* - CRDT-level: Coalesce after CRDT conversion (journal/network stage)
|
|
79
|
+
*
|
|
80
|
+
* Currently, the graph store flow applies CRDT metadata during onEdit(),
|
|
81
|
+
* so this function is not used for graph text operations.
|
|
82
|
+
* See coalesceGraphOps() for CRDT-level coalescence.
|
|
83
|
+
*
|
|
72
84
|
* Returns a new array with coalesced operations.
|
|
73
85
|
*/
|
|
74
86
|
export function coalesceTextOps(ops) {
|
|
@@ -79,12 +91,28 @@ export function coalesceTextOps(ops) {
|
|
|
79
91
|
let pendingDelete = null;
|
|
80
92
|
for (const op of ops) {
|
|
81
93
|
if (isPositionTextInsertOp(op)) {
|
|
94
|
+
// Check if operation has CRDT metadata - if so, don't coalesce it
|
|
95
|
+
const hasCRDTMetadata = op.id !== undefined;
|
|
96
|
+
if (hasCRDTMetadata) {
|
|
97
|
+
// Flush any pending operations
|
|
98
|
+
if (pendingInsert !== null) {
|
|
99
|
+
result.push(pendingInsert);
|
|
100
|
+
pendingInsert = null;
|
|
101
|
+
}
|
|
102
|
+
if (pendingDelete !== null) {
|
|
103
|
+
result.push(pendingDelete);
|
|
104
|
+
pendingDelete = null;
|
|
105
|
+
}
|
|
106
|
+
// Pass through operations with CRDT metadata unchanged
|
|
107
|
+
result.push(op);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
82
110
|
// Flush any pending delete
|
|
83
111
|
if (pendingDelete !== null) {
|
|
84
112
|
result.push(pendingDelete);
|
|
85
113
|
pendingDelete = null;
|
|
86
114
|
}
|
|
87
|
-
// Coalesce inserts
|
|
115
|
+
// Coalesce inserts (only for position-based operations without CRDT metadata)
|
|
88
116
|
if (pendingInsert === null) {
|
|
89
117
|
// Start new pending insert
|
|
90
118
|
pendingInsert = {
|
|
@@ -120,12 +148,28 @@ export function coalesceTextOps(ops) {
|
|
|
120
148
|
}
|
|
121
149
|
}
|
|
122
150
|
else if (isPositionTextDeleteOp(op)) {
|
|
151
|
+
// Check if operation has CRDT metadata - if so, don't coalesce it
|
|
152
|
+
const hasCRDTMetadata = op.deletions !== undefined;
|
|
153
|
+
if (hasCRDTMetadata) {
|
|
154
|
+
// Flush any pending operations
|
|
155
|
+
if (pendingInsert !== null) {
|
|
156
|
+
result.push(pendingInsert);
|
|
157
|
+
pendingInsert = null;
|
|
158
|
+
}
|
|
159
|
+
if (pendingDelete !== null) {
|
|
160
|
+
result.push(pendingDelete);
|
|
161
|
+
pendingDelete = null;
|
|
162
|
+
}
|
|
163
|
+
// Pass through operations with CRDT metadata unchanged
|
|
164
|
+
result.push(op);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
123
167
|
// Flush any pending insert
|
|
124
168
|
if (pendingInsert !== null) {
|
|
125
169
|
result.push(pendingInsert);
|
|
126
170
|
pendingInsert = null;
|
|
127
171
|
}
|
|
128
|
-
// Coalesce deletes
|
|
172
|
+
// Coalesce deletes (only for position-based operations without CRDT metadata)
|
|
129
173
|
if (pendingDelete === null) {
|
|
130
174
|
pendingDelete = {
|
|
131
175
|
otype: 'text.delete',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditBuffer.js","sourceRoot":"","sources":["../../src/client/EditBuffer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,EAAa;IACtC,IAAI,EAAE,CAAC,KAAK,KAAK,aAAa;QAAE,OAAO,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,WAAY,EAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAC9F,IAAI,EAAE,CAAC,KAAK,KAAK,aAAa;QAAE,OAAO,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,WAAY,EAAU,CAAC,KAAK,EAAE,CAAC;IAC1F,IAAI,EAAE,CAAC,KAAK,KAAK,WAAW;QAAI,OAAO,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,SAAU,EAAU,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAChG,0EAA0E;IAC1E,IAAI,EAAE,CAAC,KAAK,KAAK,aAAa;QAAE,OAAO,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,gBAAiB,EAAU,CAAC,QAAQ,EAAE,CAAC;IAClG,IAAI,EAAE,CAAC,KAAK,KAAK,aAAa;QAAE,OAAO,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,gBAAiB,EAAU,CAAC,QAAQ,IAAK,EAAU,CAAC,MAAM,EAAE,CAAC;IACxH,OAAO,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK,KAAK,aAAa;QACvB,KAAK,KAAK,YAAY;QACtB,KAAK,KAAK,iBAAiB;QAC3B,KAAK,KAAK,qBAAqB,CAAC;AACzC,CAAC;AAaD;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAa;IAClD,OAAO,EAAE,CAAC,KAAK,KAAK,aAAa;QAC/B,OAAQ,EAAU,CAAC,QAAQ,KAAK,QAAQ;QACxC,OAAQ,EAAU,CAAC,KAAK,KAAK,QAAQ,CAAC;AAC1C,CAAC;AAaD;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAa;IAClD,OAAO,EAAE,CAAC,KAAK,KAAK,aAAa;QAC/B,OAAQ,EAAU,CAAC,QAAQ,KAAK,QAAQ;QACxC,OAAQ,EAAU,CAAC,MAAM,KAAK,QAAQ,CAAC;AAC3C,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"EditBuffer.js","sourceRoot":"","sources":["../../src/client/EditBuffer.ts"],"names":[],"mappings":"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"}
|
package/dist/client/actions.d.ts
CHANGED
|
@@ -28,8 +28,12 @@ export declare function rebuildGraph(snapshot: Snapshot, journal: JournalEntry[]
|
|
|
28
28
|
export declare function onEdit(state: ClientState, op: Operation): ClientState;
|
|
29
29
|
/**
|
|
30
30
|
* Action: Commit edits
|
|
31
|
+
*
|
|
32
|
+
* @param state - Current client state
|
|
33
|
+
* @param description - Optional message description
|
|
34
|
+
* @param coalescingThresholdMs - Time threshold for operation coalescence (default: no coalescence)
|
|
31
35
|
*/
|
|
32
|
-
export declare function commitEdits(state: ClientState,
|
|
36
|
+
export declare function commitEdits(state: ClientState, description?: string, coalescingThresholdMs?: number): {
|
|
33
37
|
state: ClientState;
|
|
34
38
|
msg: CRDTMessage | null;
|
|
35
39
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAa,MAAM,iCAAiC,CAAC;AACrG,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtE,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAa,MAAM,iCAAiC,CAAC;AACrG,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtE,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAO/E;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,QAAQ,GAAG,WAAW,CAkB7F;AAyGD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,YAAY,EAAE,EACvB,UAAU,EAAE,SAAS,EAAE,GACtB,UAAU,CAkCZ;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,GAAG,WAAW,CA4DrE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAClB,WAAW,CAAC,EAAE,MAAM,EACpB,qBAAqB,CAAC,EAAE,MAAM,GAC7B;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAgCjD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CAU3D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAK1E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,GAAG,WAAW,CA8BjF;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAyDxF;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAoDxF;AAyCD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CA6BvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,GAAG,WAAW,CA4C1F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,EAAE,CAIpE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAE9D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,WAAW,EAAE,GACrB,WAAW,CA8Cb"}
|
package/dist/client/actions.js
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
import { nanoid } from 'nanoid';
|
|
11
11
|
import { applyMessage, applyOperation, createEmptyGraph } from '../operations/dispatcher.js';
|
|
12
12
|
import { VectorClockManager } from '../state/VectorClock.js';
|
|
13
|
-
import { isAdditiveOp, mergeValues, opDedupKey
|
|
13
|
+
import { isAdditiveOp, mergeValues, opDedupKey } from './EditBuffer.js';
|
|
14
14
|
import { TextRope, snapshot as cloneRope, compact as compactRope } from '../crdt/Rope.js';
|
|
15
|
+
import { coalesceGraphOps } from './coalesceGraphOps.js';
|
|
15
16
|
const clockManager = new VectorClockManager();
|
|
16
17
|
/**
|
|
17
18
|
* Generate a compact session ID (12-char nanoid, 2^72 entropy)
|
|
@@ -218,15 +219,11 @@ export function onEdit(state, op) {
|
|
|
218
219
|
}
|
|
219
220
|
for (const k of new Set(keysToClone)) {
|
|
220
221
|
const orig = graph.nodes[k];
|
|
222
|
+
// Shallow clone preserves TextRope references (they're mutated in place)
|
|
221
223
|
const cloned = {
|
|
222
224
|
...orig,
|
|
223
225
|
children: orig.children ? [...orig.children] : [],
|
|
224
226
|
};
|
|
225
|
-
for (const prop of Object.keys(cloned)) {
|
|
226
|
-
if (cloned[prop] instanceof TextRope) {
|
|
227
|
-
cloned[prop] = cloneRope(cloned[prop]);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
227
|
graph.nodes[k] = cloned;
|
|
231
228
|
}
|
|
232
229
|
applyOperation(graph, op, {
|
|
@@ -243,15 +240,21 @@ export function onEdit(state, op) {
|
|
|
243
240
|
}
|
|
244
241
|
/**
|
|
245
242
|
* Action: Commit edits
|
|
243
|
+
*
|
|
244
|
+
* @param state - Current client state
|
|
245
|
+
* @param description - Optional message description
|
|
246
|
+
* @param coalescingThresholdMs - Time threshold for operation coalescence (default: no coalescence)
|
|
246
247
|
*/
|
|
247
|
-
export function commitEdits(state,
|
|
248
|
+
export function commitEdits(state, description, coalescingThresholdMs) {
|
|
248
249
|
if (state.edits.ops.length === 0) {
|
|
249
250
|
return { state, msg: null };
|
|
250
251
|
}
|
|
251
252
|
const newClock = clockManager.increment(state.vectorClock, state.sessionId);
|
|
252
253
|
const newLamport = state.lamportTime + 1;
|
|
253
|
-
// Coalesce
|
|
254
|
-
const coalescedOps =
|
|
254
|
+
// Coalesce CRDT operations before sending (if threshold specified)
|
|
255
|
+
const coalescedOps = coalescingThresholdMs !== undefined
|
|
256
|
+
? coalesceGraphOps(state.edits.ops, { thresholdMs: coalescingThresholdMs })
|
|
257
|
+
: state.edits.ops;
|
|
255
258
|
const msg = {
|
|
256
259
|
id: `${state.sessionId}:${newLamport}`,
|
|
257
260
|
sessionId: state.sessionId,
|