Skip to content

Commit 87042eb

Browse files
authored
Merge pull request #164 from streamich/json-ot
Add ot-string type
2 parents b4c6a7a + fc74b3c commit 87042eb

File tree

29 files changed

+767
-107
lines changed

29 files changed

+767
-107
lines changed

.git-cz.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"json-crdt-patch",
1212
"json-equal",
1313
"json-expression",
14+
"json-ot",
1415
"json-pack",
1516
"json-patch",
1617
"json-patch-ot",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"messagepack": "^1.1.12",
9696
"msgpack-lite": "^0.1.26",
9797
"msgpack5": "^5.3.2",
98+
"ot-text": "^1.0.2",
9899
"pako": "^2.0.4",
99100
"prettier": "^2.5.1",
100101
"pretty-quick": "^3.1.3",

src/__tests__/msgpack-documents.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {JsonPackExtension, JsonPackValue} from "../json-pack";
2-
import {encodeFull} from "../json-pack/util";
1+
import {JsonPackExtension, JsonPackValue} from '../json-pack';
2+
import {encodeFull} from '../json-pack/util';
33

44
export interface JsonDocument {
55
name: string;
@@ -24,9 +24,7 @@ export const msgPackDocuments: JsonDocument[] = [
2424
},
2525
{
2626
name: 'MessagePack value in array',
27-
json: [
28-
new JsonPackValue(encodeFull(null)),
29-
],
27+
json: [new JsonPackValue(encodeFull(null))],
3028
},
3129
{
3230
name: 'MessagePack extension',
@@ -36,7 +34,7 @@ export const msgPackDocuments: JsonDocument[] = [
3634
name: 'MessagePack extension in object',
3735
json: {
3836
foo: new JsonPackExtension(1, new Uint8Array([1, 2, 3])),
39-
}
37+
},
4038
},
4139
{
4240
name: 'MessagePack extension in array',

src/demo/ot-text.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* tslint:disable no-console */
2+
3+
/**
4+
* Run this demo with:
5+
*
6+
* npx ts-node src/demo/ot-text.ts
7+
*/
8+
9+
const {type} = require('ot-text');
10+
11+
const op1 = [1, 'a'];
12+
const op2 = [3, 'b'];
13+
14+
// const op1 = [3, 'a'];
15+
// const op2 = [3, 'b'];
16+
17+
const op3 = type.transform(op1, op2, 'left');
18+
const op4 = type.transform(op2, op1, 'right');
19+
20+
console.log('op3', op3);
21+
console.log('op4', op4);

src/json-binary/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ const unwrapBinary = (value: unknown): unknown => {
3333
}
3434
case 'string': {
3535
if (item.length < minDataUri) continue;
36-
if (item.substring(0, binUriStartLength) === binUriStart) value[i] = fromBase64(item.substring(binUriStartLength));
36+
if (item.substring(0, binUriStartLength) === binUriStart)
37+
value[i] = fromBase64(item.substring(binUriStartLength));
3738
else if (item.substring(0, msgPackUriStartLength) === msgPackUriStart)
3839
value[i] = new JsonPackValue(fromBase64(item.substring(msgPackUriStartLength)));
3940
else if (item.substring(0, msgPackExtStartLength) === msgPackExtStart) value[i] = parseExtDataUri(item);
@@ -57,8 +58,8 @@ const unwrapBinary = (value: unknown): unknown => {
5758
(value as any)[key] = buf;
5859
} else if (item.substring(0, msgPackUriStartLength) === msgPackUriStart) {
5960
(value as any)[key] = new JsonPackValue(fromBase64(item.substring(msgPackUriStartLength)));
60-
}
61-
else if (item.substring(0, msgPackExtStartLength) === msgPackExtStart) (value as any)[key] = parseExtDataUri(item);
61+
} else if (item.substring(0, msgPackExtStartLength) === msgPackExtStart)
62+
(value as any)[key] = parseExtDataUri(item);
6263
}
6364
}
6465
}
@@ -67,7 +68,8 @@ const unwrapBinary = (value: unknown): unknown => {
6768
if (typeof value === 'string') {
6869
if (value.length < minDataUri) return value;
6970
if (value.substring(0, binUriStartLength) === binUriStart) return fromBase64(value.substring(binUriStartLength));
70-
if (value.substring(0, msgPackUriStartLength) === msgPackUriStart) return new JsonPackValue(fromBase64(value.substring(msgPackUriStartLength)));
71+
if (value.substring(0, msgPackUriStartLength) === msgPackUriStart)
72+
return new JsonPackValue(fromBase64(value.substring(msgPackUriStartLength)));
7173
if (value.substring(0, msgPackExtStartLength) === msgPackExtStart) return parseExtDataUri(value);
7274
else return value;
7375
}

src/json-block/Block.ts

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,12 @@ export class BasicBlock<Data, Patch> {
4343

4444
public apply(patch: Patch): void {
4545
this.model.apply(patch);
46-
this.v$.next(this.v$.getValue() + 1);
46+
this.v$.next(this.v$.getValue());
4747
}
4848
}
4949

5050
export type PatchResponse<Patch> = true | false | Patch;
5151

52-
export interface BranchDependencies<Patch> {
53-
readonly merge: (baseVersion: number, patches: Patch[]) => Promise<BranchMergeResponse<Patch>>;
54-
}
55-
5652
export interface BranchMergeResponse<Patch> {
5753
version: number;
5854
batches: PatchResponse<Patch>[][];
@@ -66,31 +62,23 @@ export class Branch<Data, Patch> {
6662
protected readonly head$: BehaviorSubject<BasicBlock<Data, Patch>>;
6763

6864
/**
69-
* List of patches applied locally to the head, but not saved
70-
* on the server. This is delta between the base and the head.
65+
* List of patches applied locally to the head, but not yet
66+
* added to the `batches` list.
7167
*/
7268
protected patches: Patch[];
7369

74-
/** Number of patches currently being merged to the server. */
75-
protected merging: number = 0;
70+
/** List of batches to be persisted on the server. */
71+
protected batches: Patch[][] = [];
7672

7773
constructor(base: BasicBlock<Data, Patch>) {
7874
this.base$ = new BehaviorSubject(base);
7975
this.patches = [];
8076
this.head$ = new BehaviorSubject(base.fork());
8177
}
8278

83-
public async merge(opts: BranchDependencies<Patch>): Promise<void> {
84-
try {
85-
const base = this.base$.getValue();
86-
const baseVersion = base.v$.getValue();
87-
const batch = [...this.patches];
88-
this.merging = batch.length;
89-
const res = await opts.merge(baseVersion, batch);
90-
91-
} catch (error) {
92-
this.merging = 0;
93-
}
79+
public cutBatch(): void {
80+
this.batches.push(this.patches);
81+
this.patches = [];
9482
}
9583

9684
/** Apply a patch locally to the head. */

src/json-block/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {Observable} from "rxjs";
1+
import type {Observable} from 'rxjs';
22

33
export interface BlockServerApi {
44
create: () => Promise<void>;
@@ -11,14 +11,14 @@ export interface BlockClientApiMergeRequest {
1111
/** Last known batch ID by the client. */
1212
v: number;
1313
/** List of patches serialized in block-specific codec. */
14-
batch: unknown[];
14+
b: unknown[];
1515
}
1616

1717
/**
1818
* There are a number of scenarios that can happen
1919
* when merging changes. The possible scenarios also depend
2020
* on the collaborative editing algorithm used for the block.
21-
*
21+
*
2222
* 1. The batch is accepted without any conflicts (changes).
2323
* 2. The batch is accepted with conflicts, hence the batch may be
2424
* be modified to resolve the conflicts. This is relevant for

src/json-crdt/codec/binary/Encoder.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class Encoder extends CrdtEncoder {
4646
delete this.literals;
4747
return this.flush();
4848
}
49-
49+
5050
protected encodeLiteralsTable(model: Model) {
5151
const literalFrequencies = new Map<string | number, number>();
5252
for (const node of model.nodes.iterate()) {
@@ -75,11 +75,9 @@ export class Encoder extends CrdtEncoder {
7575
}
7676
}
7777
const literals: unknown[] = [];
78-
for (const [literal, frequency] of literalFrequencies.entries())
79-
if (frequency > 1) literals.push(literal);
78+
for (const [literal, frequency] of literalFrequencies.entries()) if (frequency > 1) literals.push(literal);
8079
this.encodeArray(literals);
81-
for (let i = 0; i < literals.length; i++)
82-
this.literals!.set(literals[i], i);
80+
for (let i = 0; i < literals.length; i++) this.literals!.set(literals[i], i);
8381
}
8482

8583
protected encodeClockTable(data: Uint8Array) {

src/json-crdt/codec/binary/__tests__/ViewDecoder.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ describe('logical', () => {
99
doc1.api.root([1, 'asdf', false, {}, {foo: 'bar'}]).commit();
1010
doc1.api.str([1]).ins(4, '!').commit();
1111
doc1.api.str([4, 'foo']).del(1, 1).commit();
12-
doc1.api.arr([]).ins(1, [new Uint8Array([1, 2, 3])]).commit();
12+
doc1.api
13+
.arr([])
14+
.ins(1, [new Uint8Array([1, 2, 3])])
15+
.commit();
1316
doc1.api.bin([1]).del(2, 1).commit();
1417
doc1.api.arr([]).ins(6, ['a', 'b', 'c']).commit();
1518
doc1.api.arr([]).del(7, 1).commit();

src/json-crdt/codec/binary/__tests__/literals.spec.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ import {Encoder} from '../Encoder';
44
test('encodes repeating object keys into literals table', () => {
55
const encoder = new Encoder();
66
const model1 = Model.withLogicalClock();
7-
model1.api.root({
8-
foooo: {
9-
baaar: 0,
10-
}
11-
}).commit();
7+
model1.api
8+
.root({
9+
foooo: {
10+
baaar: 0,
11+
},
12+
})
13+
.commit();
1214
const model2 = Model.withLogicalClock();
13-
model2.api.root({
14-
foooo: {
15-
foooo: 0,
16-
}
17-
}).commit();
15+
model2.api
16+
.root({
17+
foooo: {
18+
foooo: 0,
19+
},
20+
})
21+
.commit();
1822
const encoded1 = encoder.encode(model1);
1923
const encoded2 = encoder.encode(model2);
2024
expect(encoded1.byteLength > encoded2.byteLength).toBe(true);

0 commit comments

Comments
 (0)