loro-crdt 0.2.8-alpha-1 → 0.4.0
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/README.md +8 -0
- package/dist/loro.d.ts +74 -59
- package/dist/loro.js +17 -106
- package/dist/loro.js.map +1 -1
- package/dist/loro.mjs +14 -76
- package/dist/loro.mjs.map +1 -1
- package/package.json +19 -6
- package/src/index.ts +83 -205
- package/vite.config.ts +1 -1
- package/.vscode/settings.json +0 -3
- package/tests/event.test.ts +0 -261
- package/tests/frontiers.test.ts +0 -30
- package/tests/misc.test.ts +0 -293
package/src/index.ts
CHANGED
|
@@ -1,40 +1,14 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
PrelimList,
|
|
6
|
-
PrelimMap,
|
|
7
|
-
PrelimText,
|
|
8
|
-
setPanicHook,
|
|
9
|
-
Transaction,
|
|
10
|
-
} from "loro-wasm";
|
|
11
|
-
import { PrelimMap } from "loro-wasm";
|
|
12
|
-
import { PrelimText } from "loro-wasm";
|
|
13
|
-
import { PrelimList } from "loro-wasm";
|
|
14
|
-
import {
|
|
15
|
-
ContainerID,
|
|
16
|
-
Loro,
|
|
17
|
-
LoroList,
|
|
18
|
-
LoroMap,
|
|
19
|
-
LoroText,
|
|
20
|
-
Transaction,
|
|
21
|
-
} from "loro-wasm";
|
|
1
|
+
export * from "loro-wasm";
|
|
2
|
+
import { Delta } from "loro-wasm";
|
|
3
|
+
import { PrelimText,PrelimList,PrelimMap } from "loro-wasm";
|
|
4
|
+
import { ContainerID, Loro, LoroList, LoroMap, LoroText , LoroTree, TreeID} from "loro-wasm";
|
|
22
5
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
cb(txn);
|
|
29
|
-
} finally {
|
|
30
|
-
txn.commit();
|
|
31
|
-
txn.free();
|
|
32
|
-
}
|
|
33
|
-
});
|
|
6
|
+
Loro.prototype.getTypedMap = function (...args) {
|
|
7
|
+
return this.getMap(...args);
|
|
8
|
+
};
|
|
9
|
+
Loro.prototype.getTypedList = function (...args) {
|
|
10
|
+
return this.getList(...args);
|
|
34
11
|
};
|
|
35
|
-
|
|
36
|
-
Loro.prototype.getTypedMap = Loro.prototype.getMap;
|
|
37
|
-
Loro.prototype.getTypedList = Loro.prototype.getList;
|
|
38
12
|
LoroList.prototype.getTyped = function (loro, index) {
|
|
39
13
|
const value = this.get(index);
|
|
40
14
|
if (typeof value === "string" && isContainerId(value)) {
|
|
@@ -43,7 +17,9 @@ LoroList.prototype.getTyped = function (loro, index) {
|
|
|
43
17
|
return value;
|
|
44
18
|
}
|
|
45
19
|
};
|
|
46
|
-
LoroList.prototype.insertTyped =
|
|
20
|
+
LoroList.prototype.insertTyped = function (...args) {
|
|
21
|
+
return this.insert(...args);
|
|
22
|
+
};
|
|
47
23
|
LoroMap.prototype.getTyped = function (loro, key) {
|
|
48
24
|
const value = this.get(key);
|
|
49
25
|
if (typeof value === "string" && isContainerId(value)) {
|
|
@@ -52,77 +28,57 @@ LoroMap.prototype.getTyped = function (loro, key) {
|
|
|
52
28
|
return value;
|
|
53
29
|
}
|
|
54
30
|
};
|
|
55
|
-
LoroMap.prototype.setTyped =
|
|
56
|
-
|
|
57
|
-
LoroText.prototype.insert = function (txn, pos, text) {
|
|
58
|
-
if (txn instanceof Loro) {
|
|
59
|
-
this.__loro_insert(txn, pos, text);
|
|
60
|
-
} else {
|
|
61
|
-
this.__txn_insert(txn, pos, text);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
LoroText.prototype.delete = function (txn, pos, len) {
|
|
66
|
-
if (txn instanceof Loro) {
|
|
67
|
-
this.__loro_delete(txn, pos, len);
|
|
68
|
-
} else {
|
|
69
|
-
this.__txn_delete(txn, pos, len);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
LoroList.prototype.insert = function (txn, pos, len) {
|
|
74
|
-
if (txn instanceof Loro) {
|
|
75
|
-
this.__loro_insert(txn, pos, len);
|
|
76
|
-
} else {
|
|
77
|
-
this.__txn_insert(txn, pos, len);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
LoroList.prototype.delete = function (txn, pos, len) {
|
|
82
|
-
if (txn instanceof Loro) {
|
|
83
|
-
this.__loro_delete(txn, pos, len);
|
|
84
|
-
} else {
|
|
85
|
-
this.__txn_delete(txn, pos, len);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
LoroMap.prototype.set = function (txn, key, value) {
|
|
90
|
-
if (txn instanceof Loro) {
|
|
91
|
-
this.__loro_insert(txn, key, value);
|
|
92
|
-
} else {
|
|
93
|
-
this.__txn_insert(txn, key, value);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
LoroMap.prototype.delete = function (txn, key) {
|
|
98
|
-
if (txn instanceof Loro) {
|
|
99
|
-
this.__loro_delete(txn, key);
|
|
100
|
-
} else {
|
|
101
|
-
this.__txn_delete(txn, key);
|
|
102
|
-
}
|
|
31
|
+
LoroMap.prototype.setTyped = function (...args) {
|
|
32
|
+
return this.set(...args);
|
|
103
33
|
};
|
|
104
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Data types supported by loro
|
|
37
|
+
*/
|
|
105
38
|
export type Value =
|
|
106
39
|
| ContainerID
|
|
107
40
|
| string
|
|
108
41
|
| number
|
|
42
|
+
| boolean
|
|
109
43
|
| null
|
|
110
44
|
| { [key: string]: Value }
|
|
45
|
+
| Uint8Array
|
|
111
46
|
| Value[];
|
|
112
47
|
|
|
48
|
+
|
|
113
49
|
export type Prelim = PrelimList | PrelimMap | PrelimText;
|
|
114
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Represents a path to identify the exact location of an event's target.
|
|
53
|
+
* The path is composed of numbers (e.g., indices of a list container) and strings
|
|
54
|
+
* (e.g., keys of a map container), indicating the absolute position of the event's source
|
|
55
|
+
* within a loro document.
|
|
56
|
+
*/
|
|
115
57
|
export type Path = (number | string)[];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The event of Loro.
|
|
61
|
+
* @prop local - Indicates whether the event is local.
|
|
62
|
+
* @prop origin - (Optional) Provides information about the origin of the event.
|
|
63
|
+
* @prop diff - Contains the differential information related to the event.
|
|
64
|
+
* @prop target - Identifies the container ID of the event's target.
|
|
65
|
+
* @prop path - Specifies the absolute path of the event's emitter, which can be an index of a list container or a key of a map container.
|
|
66
|
+
*/
|
|
67
|
+
export interface LoroEvent {
|
|
68
|
+
local: boolean;
|
|
69
|
+
origin?: string;
|
|
70
|
+
/**
|
|
71
|
+
* If true, this event was triggered by a child container.
|
|
72
|
+
*/
|
|
73
|
+
fromChildren: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* If true, this event was triggered by a checkout.
|
|
76
|
+
*/
|
|
77
|
+
fromCheckout: boolean;
|
|
78
|
+
diff: Diff;
|
|
79
|
+
target: ContainerID;
|
|
80
|
+
path: Path;
|
|
81
|
+
}
|
|
126
82
|
|
|
127
83
|
export type ListDiff = {
|
|
128
84
|
type: "list";
|
|
@@ -136,54 +92,24 @@ export type TextDiff = {
|
|
|
136
92
|
|
|
137
93
|
export type MapDiff = {
|
|
138
94
|
type: "map";
|
|
139
|
-
|
|
140
|
-
added: Record<string, Value>;
|
|
141
|
-
deleted: Record<string, Value>;
|
|
142
|
-
updated: Record<string, {
|
|
143
|
-
old: Value;
|
|
144
|
-
new: Value;
|
|
145
|
-
}>;
|
|
146
|
-
};
|
|
95
|
+
updated: Record<string, Value | undefined>;
|
|
147
96
|
};
|
|
148
97
|
|
|
149
|
-
export type
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
local: boolean;
|
|
153
|
-
origin?: string;
|
|
154
|
-
diff: Diff;
|
|
155
|
-
target: ContainerID;
|
|
156
|
-
path: Path;
|
|
98
|
+
export type TreeDiff = {
|
|
99
|
+
type: "tree";
|
|
100
|
+
diff: {target: TreeID, action: "create"|"delete" } | {target: TreeID; action:"move"; parent: TreeID};
|
|
157
101
|
}
|
|
158
102
|
|
|
103
|
+
export type Diff = ListDiff | TextDiff | MapDiff | TreeDiff;
|
|
104
|
+
|
|
159
105
|
interface Listener {
|
|
160
106
|
(event: LoroEvent): void;
|
|
161
107
|
}
|
|
162
108
|
|
|
163
|
-
const CONTAINER_TYPES = ["Map", "Text", "List"];
|
|
109
|
+
const CONTAINER_TYPES = ["Map", "Text", "List", "Tree"];
|
|
164
110
|
|
|
165
111
|
export function isContainerId(s: string): s is ContainerID {
|
|
166
|
-
|
|
167
|
-
if (s.startsWith("/")) {
|
|
168
|
-
const [_, type] = s.slice(1).split(":");
|
|
169
|
-
if (!CONTAINER_TYPES.includes(type)) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
const [id, type] = s.split(":");
|
|
174
|
-
if (!CONTAINER_TYPES.includes(type)) {
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const [counter, client] = id.split("@");
|
|
179
|
-
Number.parseInt(counter);
|
|
180
|
-
Number.parseInt(client);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return true;
|
|
184
|
-
} catch (e) {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
112
|
+
return s.startsWith("cid:");
|
|
187
113
|
}
|
|
188
114
|
|
|
189
115
|
export { Loro };
|
|
@@ -191,98 +117,50 @@ export { Loro };
|
|
|
191
117
|
declare module "loro-wasm" {
|
|
192
118
|
interface Loro {
|
|
193
119
|
subscribe(listener: Listener): number;
|
|
194
|
-
transact(f: (tx: Transaction) => void, origin?: string): void;
|
|
195
120
|
}
|
|
196
121
|
|
|
197
122
|
interface Loro<T extends Record<string, any> = Record<string, any>> {
|
|
198
|
-
getTypedMap<Key extends
|
|
123
|
+
getTypedMap<Key extends keyof T & string>(
|
|
199
124
|
name: Key,
|
|
200
125
|
): T[Key] extends LoroMap ? T[Key] : never;
|
|
201
|
-
getTypedList<Key extends
|
|
126
|
+
getTypedList<Key extends keyof T & string>(
|
|
202
127
|
name: Key,
|
|
203
128
|
): T[Key] extends LoroList ? T[Key] : never;
|
|
204
129
|
}
|
|
205
130
|
|
|
206
131
|
interface LoroList<T extends any[] = any[]> {
|
|
207
|
-
insertContainer(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
):
|
|
212
|
-
insertContainer(
|
|
213
|
-
txn: Transaction | Loro,
|
|
214
|
-
pos: number,
|
|
215
|
-
container: "List",
|
|
216
|
-
): LoroList;
|
|
217
|
-
insertContainer(
|
|
218
|
-
txn: Transaction | Loro,
|
|
219
|
-
pos: number,
|
|
220
|
-
container: "Text",
|
|
221
|
-
): LoroText;
|
|
222
|
-
insertContainer(
|
|
223
|
-
txn: Transaction | Loro,
|
|
224
|
-
pos: number,
|
|
225
|
-
container: string,
|
|
226
|
-
): never;
|
|
132
|
+
insertContainer(pos: number, container: "Map"): LoroMap;
|
|
133
|
+
insertContainer(pos: number, container: "List"): LoroList;
|
|
134
|
+
insertContainer(pos: number, container: "Text"): LoroText;
|
|
135
|
+
insertContainer(pos: number, container: "Tree"): LoroTree;
|
|
136
|
+
insertContainer(pos: number, container: string): never;
|
|
227
137
|
|
|
228
138
|
get(index: number): Value;
|
|
229
|
-
getTyped<Key extends
|
|
230
|
-
insertTyped<Key extends
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
): void;
|
|
235
|
-
insert(txn: Transaction | Loro, pos: number, value: Value | Prelim): void;
|
|
236
|
-
delete(txn: Transaction | Loro, pos: number, len: number): void;
|
|
237
|
-
subscribe(txn: Transaction | Loro, listener: Listener): number;
|
|
238
|
-
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
|
239
|
-
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
|
139
|
+
getTyped<Key extends keyof T & number>(loro: Loro, index: Key): T[Key];
|
|
140
|
+
insertTyped<Key extends keyof T & number>(pos: Key, value: T[Key]): void;
|
|
141
|
+
insert(pos: number, value: Value | Prelim): void;
|
|
142
|
+
delete(pos: number, len: number): void;
|
|
143
|
+
subscribe(txn: Loro, listener: Listener): number;
|
|
240
144
|
}
|
|
241
145
|
|
|
242
146
|
interface LoroMap<T extends Record<string, any> = Record<string, any>> {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
):
|
|
248
|
-
insertContainer(
|
|
249
|
-
txn: Transaction | Loro,
|
|
250
|
-
key: string,
|
|
251
|
-
container_type: "List",
|
|
252
|
-
): LoroList;
|
|
253
|
-
insertContainer(
|
|
254
|
-
txn: Transaction | Loro,
|
|
255
|
-
key: string,
|
|
256
|
-
container_type: "Text",
|
|
257
|
-
): LoroText;
|
|
258
|
-
insertContainer(
|
|
259
|
-
txn: Transaction | Loro,
|
|
260
|
-
key: string,
|
|
261
|
-
container_type: string,
|
|
262
|
-
): never;
|
|
147
|
+
setContainer(key: string, container_type: "Map"): LoroMap;
|
|
148
|
+
setContainer(key: string, container_type: "List"): LoroList;
|
|
149
|
+
setContainer(key: string, container_type: "Text"): LoroText;
|
|
150
|
+
setContainer(key: string, container_type: "Tree"): LoroTree;
|
|
151
|
+
setContainer(key: string, container_type: string): never;
|
|
263
152
|
|
|
264
153
|
get(key: string): Value;
|
|
265
|
-
getTyped<Key extends
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
):
|
|
269
|
-
|
|
270
|
-
setTyped<Key extends (keyof T) & string>(
|
|
271
|
-
txn: Transaction | Loro,
|
|
272
|
-
key: Key,
|
|
273
|
-
value: T[Key],
|
|
274
|
-
): void;
|
|
275
|
-
delete(txn: Transaction | Loro, key: string): void;
|
|
276
|
-
subscribe(txn: Transaction | Loro, listener: Listener): number;
|
|
277
|
-
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
|
278
|
-
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
|
154
|
+
getTyped<Key extends keyof T & string>(txn: Loro, key: Key): T[Key];
|
|
155
|
+
set(key: string, value: Value | Prelim): void;
|
|
156
|
+
setTyped<Key extends keyof T & string>(key: Key, value: T[Key]): void;
|
|
157
|
+
delete(key: string): void;
|
|
158
|
+
subscribe(txn: Loro, listener: Listener): number;
|
|
279
159
|
}
|
|
280
160
|
|
|
281
161
|
interface LoroText {
|
|
282
|
-
insert(
|
|
283
|
-
delete(
|
|
284
|
-
subscribe(txn:
|
|
285
|
-
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
|
286
|
-
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
|
162
|
+
insert(pos: number, text: string): void;
|
|
163
|
+
delete(pos: number, len: number): void;
|
|
164
|
+
subscribe(txn: Loro, listener: Listener): number;
|
|
287
165
|
}
|
|
288
166
|
}
|
package/vite.config.ts
CHANGED
package/.vscode/settings.json
DELETED
package/tests/event.test.ts
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
Delta,
|
|
4
|
-
ListDiff,
|
|
5
|
-
Loro,
|
|
6
|
-
LoroEvent,
|
|
7
|
-
MapDiff as MapDiff,
|
|
8
|
-
TextDiff,
|
|
9
|
-
} from "../src";
|
|
10
|
-
|
|
11
|
-
describe("event", () => {
|
|
12
|
-
it("target", async () => {
|
|
13
|
-
const loro = new Loro();
|
|
14
|
-
let lastEvent: undefined | LoroEvent;
|
|
15
|
-
loro.subscribe((event) => {
|
|
16
|
-
lastEvent = event;
|
|
17
|
-
});
|
|
18
|
-
const text = loro.getText("text");
|
|
19
|
-
const id = text.id;
|
|
20
|
-
text.insert(loro, 0, "123");
|
|
21
|
-
await zeroMs();
|
|
22
|
-
expect(lastEvent?.target).toEqual(id);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("path", async () => {
|
|
26
|
-
const loro = new Loro();
|
|
27
|
-
let lastEvent: undefined | LoroEvent;
|
|
28
|
-
loro.subscribe((event) => {
|
|
29
|
-
lastEvent = event;
|
|
30
|
-
});
|
|
31
|
-
const map = loro.getMap("map");
|
|
32
|
-
const subMap = map.insertContainer(loro, "sub", "Map");
|
|
33
|
-
subMap.set(loro, "0", "1");
|
|
34
|
-
await zeroMs();
|
|
35
|
-
expect(lastEvent?.path).toStrictEqual(["map", "sub"]);
|
|
36
|
-
const list = subMap.insertContainer(loro, "list", "List");
|
|
37
|
-
list.insert(loro, 0, "2");
|
|
38
|
-
const text = list.insertContainer(loro, 1, "Text");
|
|
39
|
-
await zeroMs();
|
|
40
|
-
text.insert(loro, 0, "3");
|
|
41
|
-
await zeroMs();
|
|
42
|
-
expect(lastEvent?.path).toStrictEqual(["map", "sub", "list", 1]);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("text diff", async () => {
|
|
46
|
-
const loro = new Loro();
|
|
47
|
-
let lastEvent: undefined | LoroEvent;
|
|
48
|
-
loro.subscribe((event) => {
|
|
49
|
-
lastEvent = event;
|
|
50
|
-
});
|
|
51
|
-
const text = loro.getText("t");
|
|
52
|
-
text.insert(loro, 0, "3");
|
|
53
|
-
await zeroMs();
|
|
54
|
-
expect(lastEvent?.diff).toStrictEqual(
|
|
55
|
-
{ type: "text", diff: [{ type: "insert", value: "3" }] } as TextDiff,
|
|
56
|
-
);
|
|
57
|
-
text.insert(loro, 1, "12");
|
|
58
|
-
await zeroMs();
|
|
59
|
-
expect(lastEvent?.diff).toStrictEqual(
|
|
60
|
-
{
|
|
61
|
-
type: "text",
|
|
62
|
-
diff: [{ type: "retain", len: 1 }, { type: "insert", value: "12" }],
|
|
63
|
-
} as TextDiff,
|
|
64
|
-
);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("list diff", async () => {
|
|
68
|
-
const loro = new Loro();
|
|
69
|
-
let lastEvent: undefined | LoroEvent;
|
|
70
|
-
loro.subscribe((event) => {
|
|
71
|
-
lastEvent = event;
|
|
72
|
-
});
|
|
73
|
-
const text = loro.getList("l");
|
|
74
|
-
text.insert(loro, 0, "3");
|
|
75
|
-
await zeroMs();
|
|
76
|
-
expect(lastEvent?.diff).toStrictEqual(
|
|
77
|
-
{ type: "list", diff: [{ type: "insert", value: ["3"] }] } as ListDiff,
|
|
78
|
-
);
|
|
79
|
-
text.insert(loro, 1, "12");
|
|
80
|
-
await zeroMs();
|
|
81
|
-
expect(lastEvent?.diff).toStrictEqual(
|
|
82
|
-
{
|
|
83
|
-
type: "list",
|
|
84
|
-
diff: [{ type: "retain", len: 1 }, { type: "insert", value: ["12"] }],
|
|
85
|
-
} as ListDiff,
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("map diff", async () => {
|
|
90
|
-
const loro = new Loro();
|
|
91
|
-
let lastEvent: undefined | LoroEvent;
|
|
92
|
-
loro.subscribe((event) => {
|
|
93
|
-
lastEvent = event;
|
|
94
|
-
});
|
|
95
|
-
const map = loro.getMap("m");
|
|
96
|
-
loro.transact((tx) => {
|
|
97
|
-
map.set(tx, "0", "3");
|
|
98
|
-
map.set(tx, "1", "2");
|
|
99
|
-
});
|
|
100
|
-
await zeroMs();
|
|
101
|
-
expect(lastEvent?.diff).toStrictEqual(
|
|
102
|
-
{
|
|
103
|
-
type: "map",
|
|
104
|
-
diff: {
|
|
105
|
-
added: {
|
|
106
|
-
"0": "3",
|
|
107
|
-
"1": "2",
|
|
108
|
-
},
|
|
109
|
-
deleted: {},
|
|
110
|
-
updated: {},
|
|
111
|
-
},
|
|
112
|
-
} as MapDiff,
|
|
113
|
-
);
|
|
114
|
-
loro.transact((tx) => {
|
|
115
|
-
map.set(tx, "0", "0");
|
|
116
|
-
map.set(tx, "1", "1");
|
|
117
|
-
});
|
|
118
|
-
await zeroMs();
|
|
119
|
-
expect(lastEvent?.diff).toStrictEqual(
|
|
120
|
-
{
|
|
121
|
-
type: "map",
|
|
122
|
-
diff: {
|
|
123
|
-
added: {},
|
|
124
|
-
updated: {
|
|
125
|
-
"0": { old: "3", new: "0" },
|
|
126
|
-
"1": { old: "2", new: "1" },
|
|
127
|
-
},
|
|
128
|
-
deleted: {},
|
|
129
|
-
},
|
|
130
|
-
} as MapDiff,
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe("subscribe container events", () => {
|
|
135
|
-
it("text", async () => {
|
|
136
|
-
const loro = new Loro();
|
|
137
|
-
const text = loro.getText("text");
|
|
138
|
-
let ran = 0;
|
|
139
|
-
let oneTimeRan = 0;
|
|
140
|
-
text.subscribeOnce(loro, (_) => {
|
|
141
|
-
oneTimeRan += 1;
|
|
142
|
-
});
|
|
143
|
-
const sub = text.subscribe(loro, (event) => {
|
|
144
|
-
if (!ran) {
|
|
145
|
-
expect(event.diff.diff).toStrictEqual(
|
|
146
|
-
[{ type: "insert", "value": "123" }] as Delta<string>[],
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
ran += 1;
|
|
150
|
-
expect(event.target).toBe(text.id);
|
|
151
|
-
});
|
|
152
|
-
text.insert(loro, 0, "123");
|
|
153
|
-
text.insert(loro, 1, "456");
|
|
154
|
-
await zeroMs();
|
|
155
|
-
expect(ran).toBeTruthy();
|
|
156
|
-
// subscribeOnce test
|
|
157
|
-
expect(oneTimeRan).toBe(1);
|
|
158
|
-
expect(text.toString()).toEqual("145623");
|
|
159
|
-
|
|
160
|
-
// unsubscribe
|
|
161
|
-
const oldRan = ran;
|
|
162
|
-
text.unsubscribe(loro, sub);
|
|
163
|
-
text.insert(loro, 0, "789");
|
|
164
|
-
expect(ran).toBe(oldRan);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("map subscribe deep", async () => {
|
|
168
|
-
const loro = new Loro();
|
|
169
|
-
const map = loro.getMap("map");
|
|
170
|
-
let times = 0;
|
|
171
|
-
const sub = map.subscribeDeep(loro, (event) => {
|
|
172
|
-
times += 1;
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const subMap = map.insertContainer(loro, "sub", "Map");
|
|
176
|
-
await zeroMs();
|
|
177
|
-
expect(times).toBe(1);
|
|
178
|
-
const text = subMap.insertContainer(loro, "k", "Text");
|
|
179
|
-
await zeroMs();
|
|
180
|
-
expect(times).toBe(2);
|
|
181
|
-
text.insert(loro, 0, "123");
|
|
182
|
-
await zeroMs();
|
|
183
|
-
expect(times).toBe(3);
|
|
184
|
-
|
|
185
|
-
// unsubscribe
|
|
186
|
-
map.unsubscribe(loro, sub);
|
|
187
|
-
text.insert(loro, 0, "123");
|
|
188
|
-
await zeroMs();
|
|
189
|
-
expect(times).toBe(3);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("list subscribe deep", async () => {
|
|
193
|
-
const loro = new Loro();
|
|
194
|
-
const list = loro.getList("list");
|
|
195
|
-
let times = 0;
|
|
196
|
-
const sub = list.subscribeDeep(loro, (_) => {
|
|
197
|
-
times += 1;
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
const text = list.insertContainer(loro, 0, "Text");
|
|
201
|
-
await zeroMs();
|
|
202
|
-
expect(times).toBe(1);
|
|
203
|
-
text.insert(loro, 0, "123");
|
|
204
|
-
await zeroMs();
|
|
205
|
-
expect(times).toBe(2);
|
|
206
|
-
|
|
207
|
-
// unsubscribe
|
|
208
|
-
list.unsubscribe(loro, sub);
|
|
209
|
-
text.insert(loro, 0, "123");
|
|
210
|
-
await zeroMs();
|
|
211
|
-
expect(times).toBe(2);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe("text event length should be utf16", () => {
|
|
216
|
-
it("test", async () => {
|
|
217
|
-
const loro = new Loro();
|
|
218
|
-
const text = loro.getText("text");
|
|
219
|
-
let string = "";
|
|
220
|
-
text.subscribe(loro, (event) => {
|
|
221
|
-
const diff = event.diff;
|
|
222
|
-
expect(diff.type).toBe("text");
|
|
223
|
-
if (diff.type === "text") {
|
|
224
|
-
let newString = "";
|
|
225
|
-
let pos = 0;
|
|
226
|
-
for (const delta of diff.diff) {
|
|
227
|
-
if (delta.type === "retain") {
|
|
228
|
-
pos += delta.len;
|
|
229
|
-
newString += string.slice(0, pos);
|
|
230
|
-
} else if (delta.type === "insert") {
|
|
231
|
-
newString += delta.value;
|
|
232
|
-
} else {
|
|
233
|
-
pos += delta.len;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
string = newString + string.slice(pos);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
text.insert(loro, 0, "你好");
|
|
241
|
-
await zeroMs();
|
|
242
|
-
expect(text.toString()).toBe(string);
|
|
243
|
-
|
|
244
|
-
text.insert(loro, 1, "世界");
|
|
245
|
-
await zeroMs();
|
|
246
|
-
expect(text.toString()).toBe(string);
|
|
247
|
-
|
|
248
|
-
text.insert(loro, 2, "👍");
|
|
249
|
-
await zeroMs();
|
|
250
|
-
expect(text.toString()).toBe(string);
|
|
251
|
-
|
|
252
|
-
text.insert(loro, 4, "♪(^∇^*)");
|
|
253
|
-
await zeroMs();
|
|
254
|
-
expect(text.toString()).toBe(string);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
function zeroMs(): Promise<void> {
|
|
260
|
-
return new Promise((r) => setTimeout(r));
|
|
261
|
-
}
|
package/tests/frontiers.test.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
Delta,
|
|
4
|
-
ListDiff,
|
|
5
|
-
Loro,
|
|
6
|
-
LoroEvent,
|
|
7
|
-
MapDiff as MapDiff,
|
|
8
|
-
TextDiff,
|
|
9
|
-
} from "../src";
|
|
10
|
-
|
|
11
|
-
describe("Frontiers", () => {
|
|
12
|
-
it("two clients", () => {
|
|
13
|
-
const doc = new Loro();
|
|
14
|
-
const text = doc.getText("text");
|
|
15
|
-
text.insert(doc, 0, "0");
|
|
16
|
-
const v0 = doc.frontiers();
|
|
17
|
-
const docB = new Loro();
|
|
18
|
-
docB.import(doc.exportFrom());
|
|
19
|
-
expect(docB.cmpFrontiers(v0)).toBe(0);
|
|
20
|
-
text.insert(doc, 1, "0");
|
|
21
|
-
expect(docB.cmpFrontiers(doc.frontiers())).toBe(-1);
|
|
22
|
-
const textB = docB.getText("text");
|
|
23
|
-
textB.insert(docB, 0, "0");
|
|
24
|
-
expect(docB.cmpFrontiers(doc.frontiers())).toBe(-1);
|
|
25
|
-
docB.import(doc.exportFrom());
|
|
26
|
-
expect(docB.cmpFrontiers(doc.frontiers())).toBe(1);
|
|
27
|
-
doc.import(docB.exportFrom());
|
|
28
|
-
expect(docB.cmpFrontiers(doc.frontiers())).toBe(0);
|
|
29
|
-
});
|
|
30
|
-
});
|