meridian-sdk 1.1.0 → 1.2.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/CHANGELOG.md +14 -0
- package/README.md +2 -0
- package/dist/client.d.ts +52 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +81 -1
- package/dist/client.js.map +1 -1
- package/dist/crdt/rga.d.ts +54 -0
- package/dist/crdt/rga.d.ts.map +1 -0
- package/dist/crdt/rga.js +122 -0
- package/dist/crdt/rga.js.map +1 -0
- package/dist/crdt/tree.d.ts +83 -0
- package/dist/crdt/tree.d.ts.map +1 -0
- package/dist/crdt/tree.js +179 -0
- package/dist/crdt/tree.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/sync/delta.d.ts +17 -0
- package/dist/sync/delta.d.ts.map +1 -1
- package/dist/sync/delta.js +8 -0
- package/dist/sync/delta.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +92 -1
- package/src/crdt/rga.ts +136 -0
- package/src/crdt/tree.ts +203 -0
- package/src/index.ts +5 -0
- package/src/sync/delta.ts +27 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Chunk, Effect, Stream } from "effect";
|
|
2
|
+
import { encode } from "../codec.js";
|
|
3
|
+
/**
|
|
4
|
+
* Low-level handle for a TreeCRDT — a convergent hierarchical tree.
|
|
5
|
+
*
|
|
6
|
+
* Obtained via `MeridianClient.tree()`. Supports add, move, update, and delete
|
|
7
|
+
* operations on tree nodes. Concurrent moves use Kleppmann et al. (2021)
|
|
8
|
+
* move semantics — cycles are prevented, all replicas converge.
|
|
9
|
+
*
|
|
10
|
+
* Node IDs are returned by `addNode()` as opaque strings of the form
|
|
11
|
+
* "wall_ms:logical:node_id" matching the Rust HLC serialization.
|
|
12
|
+
*/
|
|
13
|
+
export class TreeHandle {
|
|
14
|
+
roots = [];
|
|
15
|
+
crdtId;
|
|
16
|
+
clientId;
|
|
17
|
+
transport;
|
|
18
|
+
listeners = new Set();
|
|
19
|
+
opCounter = 0;
|
|
20
|
+
constructor(opts) {
|
|
21
|
+
this.crdtId = opts.crdtId;
|
|
22
|
+
this.clientId = opts.clientId;
|
|
23
|
+
this.transport = opts.transport;
|
|
24
|
+
}
|
|
25
|
+
/** Returns the current tree value. */
|
|
26
|
+
value() {
|
|
27
|
+
return { roots: this.roots };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Registers a listener called whenever the tree changes.
|
|
31
|
+
*
|
|
32
|
+
* @returns An unsubscribe function.
|
|
33
|
+
*/
|
|
34
|
+
onChange(listener) {
|
|
35
|
+
this.listeners.add(listener);
|
|
36
|
+
return () => { this.listeners.delete(listener); };
|
|
37
|
+
}
|
|
38
|
+
/** Returns an Effect Stream that emits the tree on every change. */
|
|
39
|
+
stream() {
|
|
40
|
+
return Stream.async((emit) => {
|
|
41
|
+
const unsub = this.onChange((value) => { void emit(Effect.succeed(Chunk.of(value))); });
|
|
42
|
+
return Effect.sync(unsub);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Adds a new node to the tree.
|
|
47
|
+
*
|
|
48
|
+
* @param parentId - ID of the parent node, or null for a root-level node.
|
|
49
|
+
* @param position - Fractional index string for sibling ordering (e.g. "a0", "b0").
|
|
50
|
+
* @param value - String content of the node.
|
|
51
|
+
* @param ttlMs - Optional TTL.
|
|
52
|
+
* @returns The new node's ID as a string.
|
|
53
|
+
*/
|
|
54
|
+
addNode(parentId, position, value, ttlMs) {
|
|
55
|
+
const wallMs = Date.now();
|
|
56
|
+
const logical = this.opCounter++;
|
|
57
|
+
const id = { wall_ms: wallMs, logical, node_id: this.clientId };
|
|
58
|
+
const idStr = `${wallMs}:${logical}:${this.clientId}`;
|
|
59
|
+
const op = encode({
|
|
60
|
+
Tree: {
|
|
61
|
+
AddNode: {
|
|
62
|
+
id,
|
|
63
|
+
parent_id: parentId !== null ? this.parseHlc(parentId) : null,
|
|
64
|
+
position,
|
|
65
|
+
value,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
this.transport.send({
|
|
70
|
+
Op: {
|
|
71
|
+
crdt_id: this.crdtId,
|
|
72
|
+
op_bytes: op,
|
|
73
|
+
...(ttlMs !== undefined && { ttl_ms: ttlMs }),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return idStr;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Moves a node to a new parent and/or position.
|
|
80
|
+
*
|
|
81
|
+
* @param nodeId - ID of the node to move.
|
|
82
|
+
* @param newParentId - New parent node ID, or null for root level.
|
|
83
|
+
* @param newPosition - New fractional index position.
|
|
84
|
+
* @param ttlMs - Optional TTL.
|
|
85
|
+
*/
|
|
86
|
+
moveNode(nodeId, newParentId, newPosition, ttlMs) {
|
|
87
|
+
const wallMs = Date.now();
|
|
88
|
+
const logical = this.opCounter++;
|
|
89
|
+
const opId = { wall_ms: wallMs, logical, node_id: this.clientId };
|
|
90
|
+
const op = encode({
|
|
91
|
+
Tree: {
|
|
92
|
+
MoveNode: {
|
|
93
|
+
op_id: opId,
|
|
94
|
+
node_id: this.parseHlc(nodeId),
|
|
95
|
+
new_parent_id: newParentId !== null ? this.parseHlc(newParentId) : null,
|
|
96
|
+
new_position: newPosition,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
this.transport.send({
|
|
101
|
+
Op: {
|
|
102
|
+
crdt_id: this.crdtId,
|
|
103
|
+
op_bytes: op,
|
|
104
|
+
...(ttlMs !== undefined && { ttl_ms: ttlMs }),
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Updates the value of an existing node (LWW — last write wins).
|
|
110
|
+
*
|
|
111
|
+
* @param nodeId - ID of the node to update.
|
|
112
|
+
* @param value - New string value.
|
|
113
|
+
* @param ttlMs - Optional TTL.
|
|
114
|
+
*/
|
|
115
|
+
updateNode(nodeId, value, ttlMs) {
|
|
116
|
+
const wallMs = Date.now();
|
|
117
|
+
const logical = this.opCounter++;
|
|
118
|
+
const updatedAt = { wall_ms: wallMs, logical, node_id: this.clientId };
|
|
119
|
+
const op = encode({
|
|
120
|
+
Tree: {
|
|
121
|
+
UpdateNode: {
|
|
122
|
+
id: this.parseHlc(nodeId),
|
|
123
|
+
value,
|
|
124
|
+
updated_at: updatedAt,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
this.transport.send({
|
|
129
|
+
Op: {
|
|
130
|
+
crdt_id: this.crdtId,
|
|
131
|
+
op_bytes: op,
|
|
132
|
+
...(ttlMs !== undefined && { ttl_ms: ttlMs }),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Tombstone-deletes a node. Children are preserved in the tree structure
|
|
138
|
+
* but become invisible until the node is undeleted via a concurrent move.
|
|
139
|
+
*
|
|
140
|
+
* @param nodeId - ID of the node to delete.
|
|
141
|
+
* @param ttlMs - Optional TTL.
|
|
142
|
+
*/
|
|
143
|
+
deleteNode(nodeId, ttlMs) {
|
|
144
|
+
const op = encode({
|
|
145
|
+
Tree: {
|
|
146
|
+
DeleteNode: {
|
|
147
|
+
id: this.parseHlc(nodeId),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
this.transport.send({
|
|
152
|
+
Op: {
|
|
153
|
+
crdt_id: this.crdtId,
|
|
154
|
+
op_bytes: op,
|
|
155
|
+
...(ttlMs !== undefined && { ttl_ms: ttlMs }),
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** Apply a delta received from the server. Replaces local state with authoritative tree. */
|
|
160
|
+
applyDelta(delta) {
|
|
161
|
+
this.roots = delta.roots;
|
|
162
|
+
this.emit();
|
|
163
|
+
}
|
|
164
|
+
emit() {
|
|
165
|
+
const v = { roots: this.roots };
|
|
166
|
+
for (const listener of this.listeners)
|
|
167
|
+
listener(v);
|
|
168
|
+
}
|
|
169
|
+
/** Parse an HLC string "wall_ms:logical:node_id" back to a Rust-compatible object. */
|
|
170
|
+
parseHlc(id) {
|
|
171
|
+
const parts = id.split(":");
|
|
172
|
+
return {
|
|
173
|
+
wall_ms: Number(parts[0]),
|
|
174
|
+
logical: Number(parts[1]),
|
|
175
|
+
node_id: Number(parts[2]),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.js","sourceRoot":"","sources":["../../src/crdt/tree.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { MeridianClient } from "./client.js";
|
|
2
|
-
export type { MeridianClientConfig, ClientSnapshot, DeltaEvent, CRDTSnapshotEntry, GCounterSnapshotEntry, PNCounterSnapshotEntry, ORSetSnapshotEntry, LwwRegisterSnapshotEntry, PresenceSnapshotEntry, CRDTMapSnapshotEntry, } from "./client.js";
|
|
2
|
+
export type { MeridianClientConfig, ClientSnapshot, DeltaEvent, CRDTSnapshotEntry, GCounterSnapshotEntry, PNCounterSnapshotEntry, ORSetSnapshotEntry, LwwRegisterSnapshotEntry, PresenceSnapshotEntry, CRDTMapSnapshotEntry, RGASnapshotEntry, TreeSnapshotEntry, } from "./client.js";
|
|
3
3
|
export { GCounterHandle } from "./crdt/gcounter.js";
|
|
4
4
|
export { PNCounterHandle } from "./crdt/pncounter.js";
|
|
5
5
|
export { ORSetHandle } from "./crdt/orset.js";
|
|
@@ -10,6 +10,9 @@ export { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
|
10
10
|
export type { CrdtMapValue } from "./crdt/crdtmap.js";
|
|
11
11
|
export { AwarenessHandle } from "./crdt/awareness.js";
|
|
12
12
|
export type { AwarenessEntry } from "./crdt/awareness.js";
|
|
13
|
+
export { RGAHandle } from "./crdt/rga.js";
|
|
14
|
+
export { TreeHandle } from "./crdt/tree.js";
|
|
15
|
+
export type { TreeNodeValue, TreeDelta } from "./sync/delta.js";
|
|
13
16
|
export { HttpClient } from "./transport/http.js";
|
|
14
17
|
export type { HttpClientConfig, HistoryEntry, HistoryResponse } from "./transport/http.js";
|
|
15
18
|
export { WsTransport } from "./transport/websocket.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAG3E,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGlG,OAAO,EACL,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,SAAS,EACT,aAAa,EACb,cAAc,EACd,UAAU,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,IAAI,EACJ,YAAY,EACZ,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG3D,OAAO,EACL,MAAM,EACN,MAAM,EACN,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export { LwwRegisterHandle } from "./crdt/lwwregister.js";
|
|
|
8
8
|
export { PresenceHandle } from "./crdt/presence.js";
|
|
9
9
|
export { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
10
10
|
export { AwarenessHandle } from "./crdt/awareness.js";
|
|
11
|
+
export { RGAHandle } from "./crdt/rga.js";
|
|
12
|
+
export { TreeHandle } from "./crdt/tree.js";
|
|
11
13
|
// Transport
|
|
12
14
|
export { HttpClient } from "./transport/http.js";
|
|
13
15
|
export { WsTransport } from "./transport/websocket.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAgB7C,eAAe;AACf,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG5C,YAAY;AACZ,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO;AACP,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElG,iEAAiE;AACjE,OAAO,EACL,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,qEAAqE;AACrE,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,SAAS,EACT,aAAa,EACb,cAAc,EACd,UAAU,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC;AAGrB,sCAAsC;AACtC,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAOrB,sCAAsC;AACtC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D,qCAAqC;AACrC,OAAO,EACL,MAAM,EACN,MAAM,EACN,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
|
package/dist/sync/delta.d.ts
CHANGED
|
@@ -38,6 +38,19 @@ export interface PresenceDelta {
|
|
|
38
38
|
changes: Record<string, PresenceEntryDelta | null>;
|
|
39
39
|
}
|
|
40
40
|
export declare const decodePresenceDelta: (bytes: Uint8Array) => PresenceDelta;
|
|
41
|
+
export interface RGADelta {
|
|
42
|
+
text: string;
|
|
43
|
+
}
|
|
44
|
+
export declare const decodeRGADelta: (bytes: Uint8Array) => RGADelta;
|
|
45
|
+
export interface TreeNodeValue {
|
|
46
|
+
id: string;
|
|
47
|
+
value: string;
|
|
48
|
+
children: TreeNodeValue[];
|
|
49
|
+
}
|
|
50
|
+
export interface TreeDelta {
|
|
51
|
+
roots: TreeNodeValue[];
|
|
52
|
+
}
|
|
53
|
+
export declare const decodeTreeDelta: (bytes: Uint8Array) => TreeDelta;
|
|
41
54
|
export type CrdtValueDelta = {
|
|
42
55
|
GCounter: GCounterDelta;
|
|
43
56
|
} | {
|
|
@@ -48,6 +61,10 @@ export type CrdtValueDelta = {
|
|
|
48
61
|
LwwRegister: LwwDelta;
|
|
49
62
|
} | {
|
|
50
63
|
Presence: PresenceDelta;
|
|
64
|
+
} | {
|
|
65
|
+
RGA: RGADelta;
|
|
66
|
+
} | {
|
|
67
|
+
Tree: TreeDelta;
|
|
51
68
|
};
|
|
52
69
|
export interface CRDTMapDelta {
|
|
53
70
|
deltas: Record<string, CrdtValueDelta>;
|
package/dist/sync/delta.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../../src/sync/delta.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,UAAU,KAAG,aAGvD,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;CAC3B;AAED,eAAO,MAAM,oBAAoB,GAAI,OAAO,UAAU,KAAG,cASxD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,gBAAgB,GAAI,OAAO,UAAU,KAAG,UAUpD,CAAC;AAEF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC7E,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,UAAU,KAAG,QAGlD,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC7E,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACpD;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,UAAU,KAAG,aAGvD,CAAC;AAEF,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,GAC3B;IAAE,SAAS,EAAE,cAAc,CAAA;CAAE,GAC7B;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,GACrB;IAAE,WAAW,EAAE,QAAQ,CAAA;CAAE,GACzB;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../../src/sync/delta.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,UAAU,KAAG,aAGvD,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;CAC3B;AAED,eAAO,MAAM,oBAAoB,GAAI,OAAO,UAAU,KAAG,cASxD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,gBAAgB,GAAI,OAAO,UAAU,KAAG,UAUpD,CAAC;AAEF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC7E,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,UAAU,KAAG,QAGlD,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC7E,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACpD;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,UAAU,KAAG,aAGvD,CAAC;AAEF,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,UAAU,KAAG,QAGlD,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,eAAe,GAAI,OAAO,UAAU,KAAG,SAGnD,CAAC;AAEF,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,GAC3B;IAAE,SAAS,EAAE,cAAc,CAAA;CAAE,GAC7B;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,GACrB;IAAE,WAAW,EAAE,QAAQ,CAAA;CAAE,GACzB;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,GAC3B;IAAE,GAAG,EAAE,QAAQ,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExB,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAAU,KAAG,YAGtD,CAAC"}
|
package/dist/sync/delta.js
CHANGED
|
@@ -24,6 +24,14 @@ export const decodePresenceDelta = (bytes) => {
|
|
|
24
24
|
const raw = decode(bytes);
|
|
25
25
|
return { changes: raw.changes ?? {} };
|
|
26
26
|
};
|
|
27
|
+
export const decodeRGADelta = (bytes) => {
|
|
28
|
+
const raw = decode(bytes);
|
|
29
|
+
return { text: raw.text ?? "" };
|
|
30
|
+
};
|
|
31
|
+
export const decodeTreeDelta = (bytes) => {
|
|
32
|
+
const raw = decode(bytes);
|
|
33
|
+
return { roots: raw.roots ?? [] };
|
|
34
|
+
};
|
|
27
35
|
export const decodeCRDTMapDelta = (bytes) => {
|
|
28
36
|
const raw = decode(bytes);
|
|
29
37
|
return { deltas: (raw.deltas ?? {}) };
|
package/dist/sync/delta.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/sync/delta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAiB,EAAiB,EAAE;IACtE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAA0C,CAAC;IACnE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AAC1C,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAiB,EAAkB,EAAE;IACxE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAGvB,CAAC;IACF,OAAO;QACL,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI;QAC1D,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI;KAC3D,CAAC;AACJ,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAiB,EAAc,EAAE;IAChE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAGvB,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,KAAc,EAAc,EAAE,CAC7C,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAiB,CAAC,CAAC;IAC1E,MAAM,SAAS,GAAG,CAAC,GAA+B,EAAE,EAAE,CACpD,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;AACxE,CAAC,CAAC;AAYF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAiB,EAAY,EAAE;IAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAgC,CAAC;IACzD,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;AACtC,CAAC,CAAC;AAYF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAiB,EAAiB,EAAE;IACtE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAA4D,CAAC;IACrF,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;AACxC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/sync/delta.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { LwwRegisterHandle } from "./crdt/lwwregister.js";
|
|
|
10
10
|
import { PresenceHandle } from "./crdt/presence.js";
|
|
11
11
|
import { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
12
12
|
import { AwarenessHandle } from "./crdt/awareness.js";
|
|
13
|
+
import { RGAHandle } from "./crdt/rga.js";
|
|
14
|
+
import { TreeHandle } from "./crdt/tree.js";
|
|
13
15
|
import {
|
|
14
16
|
decodeGCounterDelta,
|
|
15
17
|
decodePNCounterDelta,
|
|
@@ -17,7 +19,10 @@ import {
|
|
|
17
19
|
decodeLwwDelta,
|
|
18
20
|
decodePresenceDelta,
|
|
19
21
|
decodeCRDTMapDelta,
|
|
22
|
+
decodeRGADelta,
|
|
23
|
+
decodeTreeDelta,
|
|
20
24
|
} from "./sync/delta.js";
|
|
25
|
+
import type { TreeNodeValue } from "./sync/delta.js";
|
|
21
26
|
import { parseAndValidateToken } from "./auth/token.js";
|
|
22
27
|
import type { ServerMsg, TokenClaims } from "./schema.js";
|
|
23
28
|
import type { TokenParseError, TokenExpiredError } from "./errors.js";
|
|
@@ -56,13 +61,25 @@ export interface CRDTMapSnapshotEntry {
|
|
|
56
61
|
crdtId: string;
|
|
57
62
|
value: Readonly<CrdtMapValue>;
|
|
58
63
|
}
|
|
64
|
+
export interface RGASnapshotEntry {
|
|
65
|
+
type: "rga";
|
|
66
|
+
crdtId: string;
|
|
67
|
+
text: string;
|
|
68
|
+
}
|
|
69
|
+
export interface TreeSnapshotEntry {
|
|
70
|
+
type: "tree";
|
|
71
|
+
crdtId: string;
|
|
72
|
+
roots: TreeNodeValue[];
|
|
73
|
+
}
|
|
59
74
|
export type CRDTSnapshotEntry =
|
|
60
75
|
| GCounterSnapshotEntry
|
|
61
76
|
| PNCounterSnapshotEntry
|
|
62
77
|
| ORSetSnapshotEntry
|
|
63
78
|
| LwwRegisterSnapshotEntry
|
|
64
79
|
| PresenceSnapshotEntry
|
|
65
|
-
| CRDTMapSnapshotEntry
|
|
80
|
+
| CRDTMapSnapshotEntry
|
|
81
|
+
| RGASnapshotEntry
|
|
82
|
+
| TreeSnapshotEntry;
|
|
66
83
|
|
|
67
84
|
export interface DeltaEvent {
|
|
68
85
|
crdtId: string;
|
|
@@ -127,6 +144,8 @@ export class MeridianClient {
|
|
|
127
144
|
private readonly prHandles = new Map<string, PresenceHandle<unknown>>();
|
|
128
145
|
private readonly cmHandles = new Map<string, CRDTMapHandle>();
|
|
129
146
|
private readonly awHandles = new Map<string, AwarenessHandle<unknown>>();
|
|
147
|
+
private readonly rgaHandles = new Map<string, RGAHandle>();
|
|
148
|
+
private readonly treeHandles = new Map<string, TreeHandle>();
|
|
130
149
|
|
|
131
150
|
private readonly anyListeners = new Set<() => void>();
|
|
132
151
|
private readonly deltaListeners = new Set<(event: DeltaEvent) => void>();
|
|
@@ -358,6 +377,62 @@ export class MeridianClient {
|
|
|
358
377
|
return handle;
|
|
359
378
|
}
|
|
360
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Returns a handle for an RGA (Replicated Growable Array) CRDT — collaborative text editing.
|
|
382
|
+
*
|
|
383
|
+
* Handles are cached by `crdtId`; calling this method multiple times with the
|
|
384
|
+
* same id returns the same handle instance and creates only one subscription.
|
|
385
|
+
*
|
|
386
|
+
* @param crdtId - Unique identifier for the CRDT within this namespace.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* const doc = client.rga('doc:content');
|
|
391
|
+
* doc.insert(0, 'Hello');
|
|
392
|
+
* doc.onChange(text => console.log(text));
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
rga(crdtId: string): RGAHandle {
|
|
396
|
+
let handle = this.rgaHandles.get(crdtId);
|
|
397
|
+
if (!handle) {
|
|
398
|
+
handle = new RGAHandle({ crdtId, clientId: this.clientId, transport: this.transport });
|
|
399
|
+
this.rgaHandles.set(crdtId, handle);
|
|
400
|
+
this.transport.subscribe(crdtId);
|
|
401
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
402
|
+
}
|
|
403
|
+
return handle;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Returns a handle for a TreeCRDT — a convergent hierarchical tree.
|
|
408
|
+
*
|
|
409
|
+
* Use this for outlines, document trees, mind maps, or any nested structure
|
|
410
|
+
* that needs concurrent editing. Concurrent moves use Kleppmann (2021)
|
|
411
|
+
* move semantics: cycle-creating moves are discarded, all replicas converge.
|
|
412
|
+
*
|
|
413
|
+
* Handles are cached by `crdtId`; the same handle is returned for repeated calls.
|
|
414
|
+
*
|
|
415
|
+
* @param crdtId - Logical CRDT identifier (e.g. `"tree:outline"`).
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```ts
|
|
419
|
+
* const tree = client.tree('doc:outline');
|
|
420
|
+
* const rootId = tree.addNode(null, 'a0', 'Introduction');
|
|
421
|
+
* const childId = tree.addNode(rootId, 'a0', 'Chapter 1');
|
|
422
|
+
* tree.onChange(t => console.log(t.roots));
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
tree(crdtId: string): TreeHandle {
|
|
426
|
+
let handle = this.treeHandles.get(crdtId);
|
|
427
|
+
if (!handle) {
|
|
428
|
+
handle = new TreeHandle({ crdtId, clientId: this.clientId, transport: this.transport });
|
|
429
|
+
this.treeHandles.set(crdtId, handle);
|
|
430
|
+
this.transport.subscribe(crdtId);
|
|
431
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
432
|
+
}
|
|
433
|
+
return handle;
|
|
434
|
+
}
|
|
435
|
+
|
|
361
436
|
/**
|
|
362
437
|
* Returns a handle for an ephemeral awareness channel.
|
|
363
438
|
*
|
|
@@ -450,6 +525,10 @@ export class MeridianClient {
|
|
|
450
525
|
crdts.push({ type: "presence", crdtId, online: h.online() as { clientId: number; data: unknown; expiresAtMs: number }[] });
|
|
451
526
|
for (const [crdtId, h] of this.cmHandles)
|
|
452
527
|
crdts.push({ type: "crdtmap", crdtId, value: h.value() });
|
|
528
|
+
for (const [crdtId, h] of this.rgaHandles)
|
|
529
|
+
crdts.push({ type: "rga", crdtId, text: h.value() });
|
|
530
|
+
for (const [crdtId, h] of this.treeHandles)
|
|
531
|
+
crdts.push({ type: "tree", crdtId, roots: h.value().roots });
|
|
453
532
|
return {
|
|
454
533
|
namespace: this.namespace,
|
|
455
534
|
clientId: this.clientId,
|
|
@@ -527,6 +606,18 @@ export class MeridianClient {
|
|
|
527
606
|
if (cmHandle) {
|
|
528
607
|
try { cmHandle.applyDelta(decodeCRDTMapDelta(delta_bytes)); } catch { /* stale */ }
|
|
529
608
|
this.notifyDelta(crdt_id, "crdtmap");
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const rgaHandle = this.rgaHandles.get(crdt_id);
|
|
612
|
+
if (rgaHandle) {
|
|
613
|
+
try { rgaHandle.applyDelta(decodeRGADelta(delta_bytes)); } catch { /* stale */ }
|
|
614
|
+
this.notifyDelta(crdt_id, "rga");
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const treeHandle = this.treeHandles.get(crdt_id);
|
|
618
|
+
if (treeHandle) {
|
|
619
|
+
try { treeHandle.applyDelta(decodeTreeDelta(delta_bytes)); } catch { /* stale */ }
|
|
620
|
+
this.notifyDelta(crdt_id, "tree");
|
|
530
621
|
}
|
|
531
622
|
}
|
|
532
623
|
|
package/src/crdt/rga.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Chunk, Effect, Stream } from "effect";
|
|
2
|
+
import { encode } from "../codec.js";
|
|
3
|
+
import type { WsTransport } from "../transport/websocket.js";
|
|
4
|
+
import type { RGADelta } from "../sync/delta.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Low-level handle for an RGA (Replicated Growable Array) CRDT — collaborative text editing.
|
|
8
|
+
*
|
|
9
|
+
* Obtained via `MeridianClient.rga()`. The RGA converges concurrent edits from multiple
|
|
10
|
+
* clients using Hybrid Logical Clock IDs and left-origin insertion ordering.
|
|
11
|
+
*
|
|
12
|
+
* Operations use visible-position indices (tombstones are invisible to callers).
|
|
13
|
+
*/
|
|
14
|
+
export class RGAHandle {
|
|
15
|
+
private text = "";
|
|
16
|
+
private readonly crdtId: string;
|
|
17
|
+
private readonly clientId: number;
|
|
18
|
+
private readonly transport: WsTransport;
|
|
19
|
+
private readonly listeners = new Set<(value: string) => void>();
|
|
20
|
+
|
|
21
|
+
constructor(opts: {
|
|
22
|
+
crdtId: string;
|
|
23
|
+
clientId: number;
|
|
24
|
+
transport: WsTransport;
|
|
25
|
+
}) {
|
|
26
|
+
this.crdtId = opts.crdtId;
|
|
27
|
+
this.clientId = opts.clientId;
|
|
28
|
+
this.transport = opts.transport;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Returns the current text content. */
|
|
32
|
+
value(): string {
|
|
33
|
+
return this.text;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Registers a listener called whenever the text changes.
|
|
38
|
+
*
|
|
39
|
+
* @returns An unsubscribe function.
|
|
40
|
+
*/
|
|
41
|
+
onChange(listener: (value: string) => void): () => void {
|
|
42
|
+
this.listeners.add(listener);
|
|
43
|
+
return () => { this.listeners.delete(listener); };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Returns an Effect Stream that emits the text on every change. */
|
|
47
|
+
stream(): Stream.Stream<string, never, never> {
|
|
48
|
+
return Stream.async<string>((emit) => {
|
|
49
|
+
const unsub = this.onChange((value) => { void emit(Effect.succeed(Chunk.of(value))); });
|
|
50
|
+
return Effect.sync(unsub);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Inserts `text` at visible position `pos` (0 = before all characters).
|
|
56
|
+
* Characters are inserted one by one in order using the RGA Insert op.
|
|
57
|
+
*
|
|
58
|
+
* @param pos - Visible character position (0-indexed).
|
|
59
|
+
* @param text - String to insert.
|
|
60
|
+
* @param ttlMs - Optional TTL for the op.
|
|
61
|
+
*/
|
|
62
|
+
insert(pos: number, text: string, ttlMs?: number): void {
|
|
63
|
+
if (text.length === 0) return;
|
|
64
|
+
|
|
65
|
+
// Optimistic local update.
|
|
66
|
+
this.text = this.text.slice(0, pos) + text + this.text.slice(pos);
|
|
67
|
+
this.emit();
|
|
68
|
+
|
|
69
|
+
// Send each character as a separate RGA Insert op. The server reorders
|
|
70
|
+
// them using their HLC IDs — sending individually preserves per-char identity.
|
|
71
|
+
const wallMs = Date.now();
|
|
72
|
+
for (let i = 0; i < text.length; i++) {
|
|
73
|
+
const op = encode({
|
|
74
|
+
RGA: {
|
|
75
|
+
Insert: {
|
|
76
|
+
id: { wall_ms: wallMs, logical: i, node_id: this.clientId },
|
|
77
|
+
origin_id: pos + i === 0 ? null : null, // server resolves via WAL
|
|
78
|
+
content: text[i],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
this.transport.send({
|
|
83
|
+
Op: {
|
|
84
|
+
crdt_id: this.crdtId,
|
|
85
|
+
op_bytes: op,
|
|
86
|
+
...(ttlMs !== undefined && { ttl_ms: ttlMs }),
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Deletes `length` visible characters starting at visible position `pos`.
|
|
94
|
+
*
|
|
95
|
+
* @param pos - Visible character position (0-indexed).
|
|
96
|
+
* @param length - Number of characters to delete.
|
|
97
|
+
* @param ttlMs - Optional TTL for the op.
|
|
98
|
+
*/
|
|
99
|
+
delete(pos: number, length: number, ttlMs?: number): void {
|
|
100
|
+
if (length <= 0) return;
|
|
101
|
+
|
|
102
|
+
// Optimistic local update.
|
|
103
|
+
this.text = this.text.slice(0, pos) + this.text.slice(pos + length);
|
|
104
|
+
this.emit();
|
|
105
|
+
|
|
106
|
+
// Send a single Delete op per character position.
|
|
107
|
+
// The server resolves the actual RGA node IDs from position.
|
|
108
|
+
for (let i = 0; i < length; i++) {
|
|
109
|
+
const op = encode({
|
|
110
|
+
RGA: {
|
|
111
|
+
Delete: {
|
|
112
|
+
pos: pos + i,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
this.transport.send({
|
|
117
|
+
Op: {
|
|
118
|
+
crdt_id: this.crdtId,
|
|
119
|
+
op_bytes: op,
|
|
120
|
+
...(ttlMs !== undefined && { ttl_ms: ttlMs }),
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Apply a delta received from the server. Replaces local text with authoritative state. */
|
|
127
|
+
applyDelta(delta: RGADelta): void {
|
|
128
|
+
if (delta.text === this.text) return;
|
|
129
|
+
this.text = delta.text;
|
|
130
|
+
this.emit();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private emit(): void {
|
|
134
|
+
for (const listener of this.listeners) listener(this.text);
|
|
135
|
+
}
|
|
136
|
+
}
|