@pluv/crdt-loro 0.16.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/.eslintrc.js +4 -0
- package/.turbo/turbo-build.log +18 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/dist/index.d.mts +164 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +701 -0
- package/dist/index.mjs +681 -0
- package/package.json +41 -0
- package/src/array/CrdtLoroArray.ts +102 -0
- package/src/array/array.ts +7 -0
- package/src/array/index.ts +2 -0
- package/src/doc/CrdtLoroDoc.ts +239 -0
- package/src/doc/CrdtLoroDocFactory.ts +65 -0
- package/src/doc/doc.ts +10 -0
- package/src/doc/index.ts +3 -0
- package/src/index.ts +1 -0
- package/src/loro.ts +6 -0
- package/src/map/CrdtLoroMap.ts +90 -0
- package/src/map/index.ts +2 -0
- package/src/map/map.ts +7 -0
- package/src/object/CrdtLoroObject.ts +79 -0
- package/src/object/index.ts +2 -0
- package/src/object/object.ts +7 -0
- package/src/shared/cloneType.ts +238 -0
- package/src/shared/getLoroContainerType.ts +22 -0
- package/src/shared/index.ts +5 -0
- package/src/shared/isWrapper.ts +19 -0
- package/src/shared/toLoroValue.ts +5 -0
- package/src/text/CrdtLoroText.ts +68 -0
- package/src/text/index.ts +2 -0
- package/src/text/text.ts +5 -0
- package/src/types.ts +40 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { AbstractCrdtType, type InferCrdtStorageJson } from "@pluv/crdt";
|
|
2
|
+
import { LoroList } from "loro-crdt";
|
|
3
|
+
import type { CrdtLoroDoc } from "../doc/CrdtLoroDoc";
|
|
4
|
+
import { cloneType, getLoroContainerType, isWrapper } from "../shared";
|
|
5
|
+
import type { InferLoroJson } from "../types";
|
|
6
|
+
|
|
7
|
+
export class CrdtLoroArray<T extends unknown> extends AbstractCrdtType<
|
|
8
|
+
LoroList<T[]>,
|
|
9
|
+
InferLoroJson<T>[]
|
|
10
|
+
> {
|
|
11
|
+
public readonly initialValue: T[] | readonly T[];
|
|
12
|
+
|
|
13
|
+
private _doc: CrdtLoroDoc<any> | null = null;
|
|
14
|
+
private _initialized: boolean = false;
|
|
15
|
+
private _value: LoroList<T[]> = new LoroList();
|
|
16
|
+
|
|
17
|
+
constructor(value: T[] | readonly T[] = []) {
|
|
18
|
+
super();
|
|
19
|
+
|
|
20
|
+
this.initialValue = value.slice();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public set doc(doc: CrdtLoroDoc<any>) {
|
|
24
|
+
if (this._doc) throw new Error("Cannot overwrite array doc");
|
|
25
|
+
|
|
26
|
+
this._doc = doc;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public get length(): number {
|
|
30
|
+
return this.value.length;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public get value(): LoroList<T[]> {
|
|
34
|
+
return this._value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public set value(value: LoroList<T[]>) {
|
|
38
|
+
if (this._initialized) throw new Error("Cannot re-assign array");
|
|
39
|
+
|
|
40
|
+
this._initialized = true;
|
|
41
|
+
this._value = value;
|
|
42
|
+
|
|
43
|
+
cloneType({ source: this, target: this.value });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public delete(index: number, length: number = 1): this {
|
|
47
|
+
this._guardInitialized();
|
|
48
|
+
|
|
49
|
+
this.value.delete(index, length);
|
|
50
|
+
this._doc?.value.commit();
|
|
51
|
+
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public insert(index: number, ...items: T[]): this {
|
|
56
|
+
this._guardInitialized();
|
|
57
|
+
|
|
58
|
+
items.forEach((item, i) => {
|
|
59
|
+
if (!(item instanceof AbstractCrdtType)) {
|
|
60
|
+
this.value.insert(index + i, item);
|
|
61
|
+
this._doc?.value.commit();
|
|
62
|
+
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!isWrapper(item)) {
|
|
67
|
+
throw new Error("This type is not yet supported");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const containerType = getLoroContainerType(item);
|
|
71
|
+
const container = this.value.insertContainer(
|
|
72
|
+
index + i,
|
|
73
|
+
containerType,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
cloneType({ source: item, target: container as any });
|
|
77
|
+
if (this._doc) item.doc = this._doc;
|
|
78
|
+
|
|
79
|
+
return this;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this._doc?.value.commit();
|
|
83
|
+
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public push(...items: T[]): this {
|
|
88
|
+
return this.insert(this.length, ...items);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public unshift(...items: T[]): this {
|
|
92
|
+
return this.insert(0, ...items);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public toJson(): InferCrdtStorageJson<InferLoroJson<T>>[] {
|
|
96
|
+
return this.value.toJson();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private _guardInitialized(): void {
|
|
100
|
+
if (!this._initialized) throw new Error("Array is not yet initialized");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AbstractCrdtType,
|
|
3
|
+
DocApplyEncodedStateParams,
|
|
4
|
+
DocSubscribeCallbackParams,
|
|
5
|
+
InferCrdtStorageJson,
|
|
6
|
+
} from "@pluv/crdt";
|
|
7
|
+
import { AbstractCrdtDoc } from "@pluv/crdt";
|
|
8
|
+
import { fromUint8Array, toUint8Array } from "js-base64";
|
|
9
|
+
import type { Container, LoroEventBatch } from "loro-crdt";
|
|
10
|
+
import { Loro } from "loro-crdt";
|
|
11
|
+
import { CrdtLoroArray } from "../array/CrdtLoroArray";
|
|
12
|
+
import { CrdtLoroMap } from "../map/CrdtLoroMap";
|
|
13
|
+
import { CrdtLoroObject } from "../object/CrdtLoroObject";
|
|
14
|
+
import { CrdtLoroText } from "../text/CrdtLoroText";
|
|
15
|
+
|
|
16
|
+
export class CrdtLoroDoc<
|
|
17
|
+
TStorage extends Record<string, AbstractCrdtType<any, any>>,
|
|
18
|
+
> extends AbstractCrdtDoc<TStorage> {
|
|
19
|
+
public value: Loro = new Loro();
|
|
20
|
+
|
|
21
|
+
private _storage: TStorage;
|
|
22
|
+
|
|
23
|
+
constructor(value: TStorage = {} as TStorage) {
|
|
24
|
+
super();
|
|
25
|
+
|
|
26
|
+
this._storage = Object.entries(value).reduce((acc, [key, node]) => {
|
|
27
|
+
if (node instanceof CrdtLoroArray) {
|
|
28
|
+
const loroList = this.value.getList(key);
|
|
29
|
+
|
|
30
|
+
node.value = loroList;
|
|
31
|
+
node.doc = this;
|
|
32
|
+
|
|
33
|
+
return { ...acc, [key]: node };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (node instanceof CrdtLoroMap || node instanceof CrdtLoroObject) {
|
|
37
|
+
const loroMap = this.value.getMap(key);
|
|
38
|
+
|
|
39
|
+
node.value = loroMap;
|
|
40
|
+
node.doc = this;
|
|
41
|
+
|
|
42
|
+
return { ...acc, [key]: node };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (node instanceof CrdtLoroText) {
|
|
46
|
+
const loroText = this.value.getText(key);
|
|
47
|
+
|
|
48
|
+
node.value = loroText;
|
|
49
|
+
node.doc = this;
|
|
50
|
+
|
|
51
|
+
return { ...acc, [key]: loroText };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return acc;
|
|
55
|
+
}, {} as TStorage);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public applyEncodedState(params: DocApplyEncodedStateParams): this {
|
|
59
|
+
const update =
|
|
60
|
+
typeof params.update === "string"
|
|
61
|
+
? toUint8Array(params.update)
|
|
62
|
+
: params.update;
|
|
63
|
+
|
|
64
|
+
if (!update) return this;
|
|
65
|
+
|
|
66
|
+
this.value.import(update);
|
|
67
|
+
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public batchApplyEncodedState(
|
|
72
|
+
updates: readonly (
|
|
73
|
+
| DocApplyEncodedStateParams
|
|
74
|
+
| string
|
|
75
|
+
| null
|
|
76
|
+
| undefined
|
|
77
|
+
)[],
|
|
78
|
+
): this {
|
|
79
|
+
const _updates = updates.reduce<Uint8Array[]>((acc, item) => {
|
|
80
|
+
if (!item) return acc;
|
|
81
|
+
|
|
82
|
+
if (typeof item === "string") {
|
|
83
|
+
acc.push(toUint8Array(item));
|
|
84
|
+
|
|
85
|
+
return acc;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof item === "object") {
|
|
89
|
+
const update =
|
|
90
|
+
typeof item.update === "string"
|
|
91
|
+
? toUint8Array(item.update)
|
|
92
|
+
: item.update;
|
|
93
|
+
|
|
94
|
+
if (!update) return acc;
|
|
95
|
+
|
|
96
|
+
acc.push(update);
|
|
97
|
+
|
|
98
|
+
return acc;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return acc;
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
if (!_updates.length) return this;
|
|
105
|
+
|
|
106
|
+
if (_updates.length === 1) {
|
|
107
|
+
const update = _updates[0] ?? null;
|
|
108
|
+
|
|
109
|
+
update && this.value.import(update);
|
|
110
|
+
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.value.importUpdateBatch(_updates);
|
|
115
|
+
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* TODO
|
|
121
|
+
* @description This method is not yet supported for loro
|
|
122
|
+
*/
|
|
123
|
+
public canRedo(): boolean {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* TODO
|
|
129
|
+
* @description This method is not yet supported for loro
|
|
130
|
+
*/
|
|
131
|
+
public canUndo(): boolean {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public destroy(): void {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public get(key?: undefined): TStorage;
|
|
140
|
+
public get<TKey extends keyof TStorage>(key: TKey): TStorage[TKey];
|
|
141
|
+
public get<TKey extends keyof TStorage>(
|
|
142
|
+
key?: TKey,
|
|
143
|
+
): TStorage | TStorage[TKey] {
|
|
144
|
+
if (typeof key === "undefined") return this._storage;
|
|
145
|
+
|
|
146
|
+
return this._storage[key as TKey];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public getEncodedState(): string {
|
|
150
|
+
return fromUint8Array(this.value.exportSnapshot());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public toJson(): InferCrdtStorageJson<TStorage> {
|
|
154
|
+
return Object.entries(this._storage).reduce(
|
|
155
|
+
(acc, [key, value]) => ({ ...acc, [key]: value.toJson() }),
|
|
156
|
+
{} as InferCrdtStorageJson<TStorage>,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public isEmpty(): boolean {
|
|
161
|
+
const serialized = this.value.toJson();
|
|
162
|
+
|
|
163
|
+
return !serialized || !Object.keys(serialized).length;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* TODO
|
|
168
|
+
* @description This method is not yet supported for loro
|
|
169
|
+
*/
|
|
170
|
+
public redo(): this {
|
|
171
|
+
throw new Error("This is not yet supported");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public subscribe(
|
|
175
|
+
listener: (params: DocSubscribeCallbackParams<TStorage>) => void,
|
|
176
|
+
): () => void {
|
|
177
|
+
const fn = (event: LoroEventBatch) => {
|
|
178
|
+
const update = fromUint8Array(this.value.exportFrom());
|
|
179
|
+
|
|
180
|
+
listener({
|
|
181
|
+
doc: this,
|
|
182
|
+
local: event.local,
|
|
183
|
+
origin: event.origin ? String(event.origin) : null,
|
|
184
|
+
update,
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const subscriptionIds = Object.entries(this._storage).reduce(
|
|
189
|
+
(map, [key, crdtType]) => {
|
|
190
|
+
const container = crdtType.value as Container;
|
|
191
|
+
const subscriptionId = container.subscribe(this.value, fn);
|
|
192
|
+
|
|
193
|
+
return map.set(key, subscriptionId);
|
|
194
|
+
},
|
|
195
|
+
new Map<string, number>(),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
return () => {
|
|
199
|
+
Array.from(subscriptionIds.entries()).forEach(
|
|
200
|
+
([key, subscriptionId]) => {
|
|
201
|
+
const container = (this._storage[key]?.value ??
|
|
202
|
+
null) as Container | null;
|
|
203
|
+
|
|
204
|
+
if (!container) {
|
|
205
|
+
throw new Error("Storage could not be found");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
container.unsubscribe(this.value, subscriptionId);
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* TODO
|
|
216
|
+
* @description This method doesn't do anything yet.
|
|
217
|
+
*/
|
|
218
|
+
public track(): this {
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* TODO
|
|
224
|
+
* @description This method doesn't do anything yet. The callback will still be executed.
|
|
225
|
+
*/
|
|
226
|
+
public transact(fn: () => void): this {
|
|
227
|
+
fn();
|
|
228
|
+
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* TODO
|
|
234
|
+
* @description This method is not yet supported for loro
|
|
235
|
+
*/
|
|
236
|
+
public undo(): this {
|
|
237
|
+
throw new Error("This is not yet supported");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { AbstractCrdtType } from "@pluv/crdt";
|
|
2
|
+
import { AbstractCrdtDocFactory } from "@pluv/crdt";
|
|
3
|
+
import { CrdtLoroArray } from "../array";
|
|
4
|
+
import { CrdtLoroMap } from "../map";
|
|
5
|
+
import { CrdtLoroObject } from "../object";
|
|
6
|
+
import { CrdtLoroText } from "../text";
|
|
7
|
+
import { CrdtLoroDoc } from "./CrdtLoroDoc";
|
|
8
|
+
|
|
9
|
+
export class CrdtLoroDocFactory<
|
|
10
|
+
TStorage extends Record<string, AbstractCrdtType<any, any>>,
|
|
11
|
+
> extends AbstractCrdtDocFactory<TStorage> {
|
|
12
|
+
private _initialStorage: () => TStorage;
|
|
13
|
+
|
|
14
|
+
constructor(initialStorage: () => TStorage = () => ({}) as TStorage) {
|
|
15
|
+
super();
|
|
16
|
+
|
|
17
|
+
this._initialStorage = initialStorage;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public getEmpty(): CrdtLoroDoc<TStorage> {
|
|
21
|
+
return new CrdtLoroDoc<TStorage>();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public getFactory(
|
|
25
|
+
initialStorage?: (() => TStorage) | undefined,
|
|
26
|
+
): CrdtLoroDocFactory<TStorage> {
|
|
27
|
+
return new CrdtLoroDocFactory<TStorage>(
|
|
28
|
+
initialStorage ?? this._initialStorage,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public getFresh(): CrdtLoroDoc<TStorage> {
|
|
33
|
+
const storage = this._initialStorage();
|
|
34
|
+
|
|
35
|
+
return new CrdtLoroDoc<TStorage>(
|
|
36
|
+
Object.entries(storage).reduce((acc, [key, node]) => {
|
|
37
|
+
if (node instanceof CrdtLoroArray) {
|
|
38
|
+
return { ...acc, [key]: new CrdtLoroArray([]) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (node instanceof CrdtLoroMap) {
|
|
42
|
+
return { ...acc, [key]: new CrdtLoroMap([]) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (node instanceof CrdtLoroObject) {
|
|
46
|
+
return { ...acc, [key]: new CrdtLoroObject({}) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (node instanceof CrdtLoroText) {
|
|
50
|
+
return { ...acc, [key]: new CrdtLoroText("") };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return acc;
|
|
54
|
+
}, {} as TStorage),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public getInitialized(
|
|
59
|
+
initialStorage?: () => TStorage,
|
|
60
|
+
): CrdtLoroDoc<TStorage> {
|
|
61
|
+
return new CrdtLoroDoc<TStorage>(
|
|
62
|
+
initialStorage?.() ?? this._initialStorage(),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/doc/doc.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AbstractCrdtType } from "@pluv/crdt";
|
|
2
|
+
import { CrdtLoroDocFactory } from "./CrdtLoroDocFactory";
|
|
3
|
+
|
|
4
|
+
export const doc = <
|
|
5
|
+
TStorage extends Record<string, AbstractCrdtType<any, any>>,
|
|
6
|
+
>(
|
|
7
|
+
value: () => TStorage = () => ({}) as TStorage,
|
|
8
|
+
): CrdtLoroDocFactory<TStorage> => {
|
|
9
|
+
return new CrdtLoroDocFactory<TStorage>(value);
|
|
10
|
+
};
|
package/src/doc/index.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as loro from "./loro";
|
package/src/loro.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { CrdtLoroArray, array } from "./array";
|
|
2
|
+
export { CrdtLoroDoc, doc } from "./doc";
|
|
3
|
+
export { CrdtLoroMap, map } from "./map";
|
|
4
|
+
export { CrdtLoroObject, object } from "./object";
|
|
5
|
+
export { CrdtLoroText, text } from "./text";
|
|
6
|
+
export type { InferLoroJson, InferLoroType } from "./types";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { InferCrdtStorageJson } from "@pluv/crdt";
|
|
2
|
+
import { AbstractCrdtType } from "@pluv/crdt";
|
|
3
|
+
import { LoroMap } from "loro-crdt";
|
|
4
|
+
import type { CrdtLoroDoc } from "../doc/CrdtLoroDoc";
|
|
5
|
+
import { cloneType, getLoroContainerType, isWrapper } from "../shared";
|
|
6
|
+
import type { InferLoroJson } from "../types";
|
|
7
|
+
|
|
8
|
+
export class CrdtLoroMap<T extends unknown> extends AbstractCrdtType<
|
|
9
|
+
LoroMap<Record<string, T>>,
|
|
10
|
+
Record<string, InferLoroJson<T>>
|
|
11
|
+
> {
|
|
12
|
+
public readonly initialValue: readonly (readonly [key: string, value: T])[];
|
|
13
|
+
|
|
14
|
+
private _doc: CrdtLoroDoc<any> | null = null;
|
|
15
|
+
private _initialized: boolean = false;
|
|
16
|
+
private _value: LoroMap<Record<string, T>> = new LoroMap();
|
|
17
|
+
|
|
18
|
+
constructor(value: readonly (readonly [key: string, value: T])[] = []) {
|
|
19
|
+
super();
|
|
20
|
+
|
|
21
|
+
this.initialValue = value.map(
|
|
22
|
+
([k, v]) => [k, v] as [key: string, value: T],
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public set doc(doc: CrdtLoroDoc<any>) {
|
|
27
|
+
if (this._doc) throw new Error("Cannot overwrite array doc");
|
|
28
|
+
|
|
29
|
+
this._doc = doc;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public get size(): number {
|
|
33
|
+
return this.value.size;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public get value(): LoroMap<Record<string, T>> {
|
|
37
|
+
return this._value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public set value(value: LoroMap<Record<string, T>>) {
|
|
41
|
+
if (this._initialized) throw new Error("Cannot re-assign map");
|
|
42
|
+
|
|
43
|
+
this._initialized = true;
|
|
44
|
+
this._value = value;
|
|
45
|
+
|
|
46
|
+
cloneType({ source: this, target: this.value });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public delete(prop: string): this {
|
|
50
|
+
this._guardInitialized();
|
|
51
|
+
|
|
52
|
+
this.value.delete(prop);
|
|
53
|
+
this._doc?.value.commit();
|
|
54
|
+
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public set(prop: string, value: T): this {
|
|
59
|
+
this._guardInitialized();
|
|
60
|
+
|
|
61
|
+
if (!(value instanceof AbstractCrdtType)) {
|
|
62
|
+
this.value.set(prop, value);
|
|
63
|
+
this._doc?.value.commit();
|
|
64
|
+
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!isWrapper(value)) {
|
|
69
|
+
throw new Error("This type is not yet supported");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const containerType = getLoroContainerType(value);
|
|
73
|
+
const container = this.value.setContainer(prop, containerType);
|
|
74
|
+
|
|
75
|
+
cloneType({ source: value, target: container as any });
|
|
76
|
+
if (this._doc) value.doc = this._doc;
|
|
77
|
+
|
|
78
|
+
this._doc?.value.commit();
|
|
79
|
+
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public toJson(): InferCrdtStorageJson<Record<string, InferLoroJson<T>>> {
|
|
84
|
+
return this.value.toJson();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private _guardInitialized(): void {
|
|
88
|
+
if (!this._initialized) throw new Error("Array is not yet initialized");
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/map/index.ts
ADDED
package/src/map/map.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { AbstractCrdtType, type InferCrdtStorageJson } from "@pluv/crdt";
|
|
2
|
+
import { LoroMap } from "loro-crdt";
|
|
3
|
+
import type { CrdtLoroDoc } from "../doc/CrdtLoroDoc";
|
|
4
|
+
import { cloneType, getLoroContainerType, isWrapper } from "../shared";
|
|
5
|
+
import type { InferLoroJson } from "../types";
|
|
6
|
+
|
|
7
|
+
export class CrdtLoroObject<
|
|
8
|
+
T extends Record<string, any>,
|
|
9
|
+
> extends AbstractCrdtType<LoroMap<T>, InferLoroJson<T>> {
|
|
10
|
+
public readonly initialValue: readonly (readonly [key: string, value: T])[];
|
|
11
|
+
|
|
12
|
+
private _doc: CrdtLoroDoc<any> | null = null;
|
|
13
|
+
private _initialized: boolean = false;
|
|
14
|
+
private _value: LoroMap<T> = new LoroMap();
|
|
15
|
+
|
|
16
|
+
constructor(value: T) {
|
|
17
|
+
super();
|
|
18
|
+
|
|
19
|
+
this.initialValue = Object.entries(value).map(
|
|
20
|
+
([k, v]) => [k, v] as [key: string, value: T],
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public set doc(doc: CrdtLoroDoc<any>) {
|
|
25
|
+
if (this._doc) throw new Error("Cannot overwrite array doc");
|
|
26
|
+
|
|
27
|
+
this._doc = doc;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public get size(): number {
|
|
31
|
+
return this.value.size;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public get value(): LoroMap<T> {
|
|
35
|
+
return this._value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public set value(value: LoroMap<T>) {
|
|
39
|
+
if (this._initialized) throw new Error("Cannot re-assign map");
|
|
40
|
+
|
|
41
|
+
this._initialized = true;
|
|
42
|
+
this._value = value;
|
|
43
|
+
|
|
44
|
+
cloneType({ source: this, target: this.value });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public set(prop: string, value: T): this {
|
|
48
|
+
this._guardInitialized();
|
|
49
|
+
|
|
50
|
+
if (!(value instanceof AbstractCrdtType)) {
|
|
51
|
+
this.value.set(prop, value);
|
|
52
|
+
this._doc?.value.commit();
|
|
53
|
+
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!isWrapper(value)) {
|
|
58
|
+
throw new Error("This type is not yet supported");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const containerType = getLoroContainerType(value);
|
|
62
|
+
const container = this.value.setContainer(prop, containerType);
|
|
63
|
+
|
|
64
|
+
cloneType({ source: value, target: container as any });
|
|
65
|
+
if (this._doc) value.doc = this._doc;
|
|
66
|
+
|
|
67
|
+
this._doc?.value.commit();
|
|
68
|
+
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public toJson(): InferCrdtStorageJson<InferLoroJson<T>> {
|
|
73
|
+
return this.value.toJson() as InferCrdtStorageJson<InferLoroJson<T>>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private _guardInitialized(): void {
|
|
77
|
+
if (!this._initialized) throw new Error("Array is not yet initialized");
|
|
78
|
+
}
|
|
79
|
+
}
|