Skip to content

Commit 8dbf09f

Browse files
authored
Merge pull request #540 from streamich/extensions-3
Extensions 3
2 parents 2741edc + f17efa2 commit 8dbf09f

File tree

10 files changed

+164
-40
lines changed

10 files changed

+164
-40
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* tslint:disable no-console */
2+
3+
/**
4+
* Run this demo with:
5+
*
6+
* npx nodemon -q -x ts-node src/json-crdt-extensions/cnt/__demos__/docs.ts
7+
*/
8+
9+
import {Model, s} from '../../../json-crdt';
10+
import {CntExt} from '..';
11+
12+
console.clear();
13+
14+
const model = Model.withLogicalClock(1234);
15+
16+
model.ext.register(CntExt);
17+
18+
model.api.root({
19+
counter: CntExt.new(1),
20+
});
21+
console.log(model + '');
22+
23+
// Excess use only ...
24+
// 2-3 days for finding damages ...
25+
// ..
26+
27+
const api = model.api.in(['counter']).asExt(CntExt);
28+
const values = api.view();
29+
30+
console.log(values);
31+
32+
api.inc(10);
33+
34+
console.log(model + '');

src/json-crdt-extensions/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './mval';
2+
export * from './cnt';
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* tslint:disable no-console */
2+
3+
/**
4+
* Run this demo with:
5+
*
6+
* npx nodemon -q -x ts-node src/json-crdt-extensions/mval/__demos__/docs.ts
7+
*/
8+
9+
import {Model, s} from '../../../json-crdt';
10+
import {MvalExt} from '..';
11+
12+
console.clear();
13+
14+
const model = Model.withLogicalClock(1234);
15+
16+
model.ext.register(MvalExt);
17+
18+
model.api.root({
19+
score: MvalExt.new(1),
20+
});
21+
console.log(model + '');
22+
23+
const api = model.api.in(['score']).asExt(MvalExt);
24+
const values = api.view();
25+
26+
console.log(values);
27+
28+
api.set(s.con(2));
29+
30+
console.log(model + '');

src/json-crdt-extensions/mval/__demos__/usage.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,26 @@
77
*/
88

99
import {Model, s} from '../../../json-crdt';
10-
import {ValueMvExt} from '..';
10+
import {MvalExt} from '..';
1111

1212
console.clear();
1313

1414
const model = Model.withLogicalClock(1234);
1515

16-
model.ext.register(ValueMvExt);
16+
model.ext.register(MvalExt);
1717

1818
model.api.root({
1919
obj: {
20-
mv: ValueMvExt.new(s.con(1)),
20+
name: s.con('John'),
21+
score: MvalExt.new(s.con(1)),
2122
},
2223
});
2324

2425
console.log('');
2526
console.log('Initial value:');
2627
console.log(model + '');
2728

28-
const api = model.api.in(['obj', 'mv']).asExt(ValueMvExt);
29+
const api = model.api.in(['obj', 'score']).asExt(MvalExt);
2930

3031
api.set(s.con(5));
3132

@@ -35,7 +36,7 @@ console.log(model + '');
3536

3637
const model2 = model.fork();
3738

38-
const api2 = model2.api.in(['obj', 'mv']).asExt(ValueMvExt);
39+
const api2 = model2.api.in(['obj', 'score']).asExt(MvalExt);
3940

4041
api.set(s.con(10));
4142
api2.set(s.con(20));
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* tslint:disable no-console */
2+
3+
/**
4+
* Run this demo with:
5+
*
6+
* npx nodemon -q -x ts-node src/json-crdt-extensions/mval/__demos__/view.ts
7+
*/
8+
9+
import {Model, s} from '../../../json-crdt';
10+
import {MvalExt} from '..';
11+
12+
console.clear();
13+
14+
const model = Model.withLogicalClock(1234);
15+
16+
model.ext.register(MvalExt);
17+
18+
model.api.root(MvalExt.new(s.con(1)));
19+
20+
console.log('');
21+
console.log('Model with extension:');
22+
console.log(model + '');
23+
24+
const model2 = Model.fromBinary(model.toBinary());
25+
26+
console.log('');
27+
console.log('Model not aware of extension:');
28+
console.log(model2 + '');

src/json-crdt-extensions/mval/__tests__/ValueMv.spec.ts renamed to src/json-crdt-extensions/mval/__tests__/MvalExt.spec.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import {ValueMvExt} from '..';
1+
import {MvalExt} from '..';
22
import {Model} from '../../../json-crdt/model';
33

44
test('can set new values in single fork', () => {
55
const model = Model.withLogicalClock();
6-
model.ext.register(ValueMvExt);
6+
model.ext.register(MvalExt);
77
model.api.root({
8-
mv: ValueMvExt.new(1),
8+
mv: MvalExt.new(1),
99
});
1010
expect(model.view()).toEqual({mv: [1]});
11-
const register = model.api.in(['mv']).asExt(ValueMvExt);
11+
const register = model.api.in(['mv']).asExt(MvalExt);
1212
register.set(2);
1313
expect(model.view()).toEqual({mv: [2]});
1414
register.set(3);
@@ -17,11 +17,11 @@ test('can set new values in single fork', () => {
1717

1818
test('removes tombstones on insert', () => {
1919
const model = Model.withLogicalClock();
20-
model.ext.register(ValueMvExt);
20+
model.ext.register(MvalExt);
2121
model.api.root({
22-
mv: ValueMvExt.new(1),
22+
mv: MvalExt.new(1),
2323
});
24-
const register = model.api.in(['mv']).asExt(ValueMvExt);
24+
const register = model.api.in(['mv']).asExt(MvalExt);
2525
expect(register.node.data.size()).toBe(1);
2626
register.set(2);
2727
expect(register.node.data.size()).toBe(1);
@@ -33,13 +33,13 @@ test('removes tombstones on insert', () => {
3333

3434
test('contains two values when two forks set value concurrently', () => {
3535
const model1 = Model.withLogicalClock();
36-
model1.ext.register(ValueMvExt);
36+
model1.ext.register(MvalExt);
3737
model1.api.root({
38-
mv: ValueMvExt.new(1),
38+
mv: MvalExt.new(1),
3939
});
4040
const model2 = model1.fork();
41-
const register1 = model1.api.in(['mv']).asExt(ValueMvExt);
42-
const register2 = model2.api.in(['mv']).asExt(ValueMvExt);
41+
const register1 = model1.api.in(['mv']).asExt(MvalExt);
42+
const register2 = model2.api.in(['mv']).asExt(MvalExt);
4343
register1.set(2);
4444
register2.set(3);
4545
expect(model1.view()).toEqual({mv: [2]});
@@ -56,13 +56,13 @@ test('contains two values when two forks set value concurrently', () => {
5656

5757
test('contains one value when a fork overwrites a register', () => {
5858
const model1 = Model.withLogicalClock();
59-
model1.ext.register(ValueMvExt);
59+
model1.ext.register(MvalExt);
6060
model1.api.root({
61-
mv: ValueMvExt.new(1),
61+
mv: MvalExt.new(1),
6262
});
6363
const model2 = model1.fork();
64-
const register1 = model1.api.in(['mv']).asExt(ValueMvExt);
65-
const register2 = model2.api.in(['mv']).asExt(ValueMvExt);
64+
const register1 = model1.api.in(['mv']).asExt(MvalExt);
65+
const register2 = model2.api.in(['mv']).asExt(MvalExt);
6666
register1.set(2);
6767
register2.set(3);
6868
model1.applyPatch(model2.api.flush());

src/json-crdt-extensions/mval/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {ITimestampStruct} from '../../json-crdt-patch/clock';
77
import type {ArrNode} from '../../json-crdt/nodes/arr/ArrNode';
88
import type {ExtensionDefinition} from '../../json-crdt';
99

10-
export const ValueMvExt: ExtensionDefinition<ArrNode, ValueMv, ValueMvApi> = {
10+
export const MvalExt: ExtensionDefinition<ArrNode, ValueMv, ValueMvApi> = {
1111
id: ExtensionId.mval,
1212
name: 'mval',
1313
new: (value: unknown | ITimestampStruct) =>

src/json-crdt/extensions/Extensions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class Extensions implements Printable {
2828
.map((k) => +k)
2929
.sort();
3030
return (
31-
this.constructor.name +
31+
'extensions' +
3232
printTree(
3333
tab,
3434
keys.map((k) => (tab) => `${k}: ${this.ext[k].name}`),

src/json-crdt/extensions/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Extensions
2+
3+
Extensions allow to create new node types out of the existing built-in types:
4+
`con`, `val`, `obj`, `vec`, `str`, `bin`, `arr`.
5+
6+
Each extension has a globally unique ID, which is an 8-bit unsigned integer.
7+
Thus, only 256 extensions can be defined at the same time.
8+
9+
Extensions do not modify in any shape the JSON CRDT, nor JSON CRDT Patch
10+
protocols, instead they build on top of the `vec` node type. An extension node
11+
is a `vec` node with a specific structure, and a specific interpretation of the
12+
elements of the `vec` node.
13+
14+
An extension `vec` node follows the following structure: it is a 2-tuple, where
15+
the first element in the extension *header* and the second element is the
16+
extension *payload*.
17+
18+
The extension *header* is a `con` node, which holds a 3 byte `Uint8Array` with
19+
the following octets: (1) the extension ID, (2) the session ID modulo 256, and
20+
(3) the time sequence modulo 256.
21+
22+
The extension *payload* is any JSON CRDT node with any value, which is specific
23+
to the extension.
24+
25+
```
26+
vec
27+
├─ 0: con Uin8Array { <ext_id>, <sid_mod_256>, <time_mod_256> }
28+
└─ 1: any
29+
```

src/json-crdt/nodes/vec/__tests__/extension.spec.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import {ValueMvExt} from '../../../../json-crdt-extensions/mval';
1+
import {MvalExt} from '../../../../json-crdt-extensions/mval';
22
import {konst} from '../../../../json-crdt-patch/builder/Konst';
33
import {Model} from '../../../../json-crdt/model';
44

55
test('can specify extension name', () => {
6-
expect(ValueMvExt.name).toBe('mval');
6+
expect(MvalExt.name).toBe('mval');
77
});
88

99
test('can create a new multi-value register', () => {
1010
const model = Model.withLogicalClock();
11-
model.ext.register(ValueMvExt);
11+
model.ext.register(MvalExt);
1212
model.api.root({
13-
mv: ValueMvExt.new(),
13+
mv: MvalExt.new(),
1414
});
1515
expect(model.view()).toEqual({
1616
mv: [],
@@ -19,9 +19,9 @@ test('can create a new multi-value register', () => {
1919

2020
test('can provide initial value', () => {
2121
const model = Model.withLogicalClock();
22-
model.ext.register(ValueMvExt);
22+
model.ext.register(MvalExt);
2323
model.api.root({
24-
mv: ValueMvExt.new({foo: 'bar'}),
24+
mv: MvalExt.new({foo: 'bar'}),
2525
});
2626
expect(model.view()).toEqual({
2727
mv: [{foo: 'bar'}],
@@ -30,22 +30,22 @@ test('can provide initial value', () => {
3030

3131
test('can read view from node or API node', () => {
3232
const model = Model.withLogicalClock();
33-
model.ext.register(ValueMvExt);
33+
model.ext.register(MvalExt);
3434
model.api.root({
35-
mv: ValueMvExt.new('foo'),
35+
mv: MvalExt.new('foo'),
3636
});
37-
const api = model.api.in('mv').asExt(ValueMvExt);
37+
const api = model.api.in('mv').asExt(MvalExt);
3838
expect(api.view()).toEqual(['foo']);
3939
expect(api.node.view()).toEqual(['foo']);
4040
});
4141

4242
test('exposes API to edit extension data', () => {
4343
const model = Model.withLogicalClock();
44-
model.ext.register(ValueMvExt);
44+
model.ext.register(MvalExt);
4545
model.api.root({
46-
mv: ValueMvExt.new(),
46+
mv: MvalExt.new(),
4747
});
48-
const nodeApi = model.api.in('mv').asExt(ValueMvExt);
48+
const nodeApi = model.api.in('mv').asExt(MvalExt);
4949
nodeApi.set(konst('lol'));
5050
expect(model.view()).toEqual({
5151
mv: ['lol'],
@@ -55,9 +55,9 @@ test('exposes API to edit extension data', () => {
5555
describe('extension validity checks', () => {
5656
test('does not treat ArrNode as extension if header is too long', () => {
5757
const model = Model.withLogicalClock();
58-
model.ext.register(ValueMvExt);
58+
model.ext.register(MvalExt);
5959
model.api.root({
60-
mv: ValueMvExt.new(),
60+
mv: MvalExt.new(),
6161
});
6262
const buf = new Uint8Array(4);
6363
buf.set(model.api.const(['mv', 0]).node.view() as Uint8Array, 0);
@@ -70,9 +70,9 @@ describe('extension validity checks', () => {
7070

7171
test('does not treat ArrNode as extension if header sid is wrong', () => {
7272
const model = Model.withLogicalClock();
73-
model.ext.register(ValueMvExt);
73+
model.ext.register(MvalExt);
7474
model.api.root({
75-
mv: ValueMvExt.new(),
75+
mv: MvalExt.new(),
7676
});
7777
const buf = model.api.const(['mv', 0]).node.view() as Uint8Array;
7878
buf[1] += 1;
@@ -84,9 +84,9 @@ describe('extension validity checks', () => {
8484

8585
test('does not treat ArrNode as extension if header time is wrong', () => {
8686
const model = Model.withLogicalClock();
87-
model.ext.register(ValueMvExt);
87+
model.ext.register(MvalExt);
8888
model.api.root({
89-
mv: ValueMvExt.new(),
89+
mv: MvalExt.new(),
9090
});
9191
const buf = model.api.const(['mv', 0]).node.view() as Uint8Array;
9292
buf[2] += 1;

0 commit comments

Comments
 (0)