loro-crdt 0.3.0 → 0.4.0-alpha.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/src/index.ts CHANGED
@@ -1,40 +1,14 @@
1
- export {
2
- LoroList,
3
- LoroMap,
4
- LoroText,
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
- export type { ContainerID, ContainerType } from "loro-wasm";
24
-
25
- Loro.prototype.transact = function (cb, origin) {
26
- this.__raw__transactionWithOrigin(origin, (txn: Transaction) => {
27
- try {
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 = LoroList.prototype.insert;
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 = LoroMap.prototype.set;
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
- export type Delta<T> = {
117
- type: "insert";
118
- value: T;
119
- } | {
120
- type: "delete";
121
- len: number;
122
- } | {
123
- type: "retain";
124
- len: number;
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
- diff: {
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 Diff = ListDiff | TextDiff | MapDiff;
150
-
151
- export interface LoroEvent {
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
- try {
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 (keyof T) & string>(
123
+ getTypedMap<Key extends keyof T & string>(
199
124
  name: Key,
200
125
  ): T[Key] extends LoroMap ? T[Key] : never;
201
- getTypedList<Key extends (keyof T) & string>(
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
- txn: Transaction | Loro,
209
- pos: number,
210
- container: "Map",
211
- ): LoroMap;
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 (keyof T) & number>(loro: Loro, index: Key): T[Key];
230
- insertTyped<Key extends (keyof T) & number>(
231
- txn: Transaction | Loro,
232
- pos: Key,
233
- value: T[Key],
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
- insertContainer(
244
- txn: Transaction | Loro,
245
- key: string,
246
- container_type: "Map",
247
- ): LoroMap;
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 (keyof T) & string>(
266
- txn: Loro,
267
- key: Key,
268
- ): T[Key];
269
- set(txn: Transaction | Loro, key: string, value: Value | Prelim): void;
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(txn: Transaction | Loro, pos: number, text: string): void;
283
- delete(txn: Transaction | Loro, pos: number, len: number): void;
284
- subscribe(txn: Transaction | Loro, listener: Listener): number;
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
@@ -4,5 +4,5 @@ import { defineConfig } from "vite";
4
4
 
5
5
  export default defineConfig({
6
6
  plugins: [wasm()],
7
- test: {},
7
+ // test: {},
8
8
  });
@@ -1,3 +0,0 @@
1
- {
2
- "deno.enable": false
3
- }
@@ -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
- }
@@ -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
- });