From 498babba28dea0f1a0f8a9690a7346d95869b5d9 Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 6 Mar 2024 15:09:49 +0100 Subject: [PATCH 1/9] =?UTF-8?q?docs(json-crdt):=20=E2=9C=8F=EF=B8=8F=20add?= =?UTF-8?q?=20extension=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/extensions/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/json-crdt/extensions/README.md diff --git a/src/json-crdt/extensions/README.md b/src/json-crdt/extensions/README.md new file mode 100644 index 0000000000..f479a894d5 --- /dev/null +++ b/src/json-crdt/extensions/README.md @@ -0,0 +1,29 @@ +# Extensions + +Extensions allow to create new node types out of the existing built-in types: +`con`, `val`, `obj`, `vec`, `str`, `bin`, `arr`. + +Each extension has a globally unique ID, which is an 8-bit unsigned integer. +Thus, only 256 extensions can be defined at the same time. + +Extensions do not modify in any shape the JSON CRDT, nor JSON CRDT Patch +protocols, instead they build on top of the `vec` node type. An extension node +is a `vec` node with a specific structure, and a specific interpretation of the +elements of the `vec` node. + +An extension `vec` node follows the following structure: it is a 2-tuple, where +the first element in the extension *header* and the second element is the +extension *payload*. + +The extension *header* is a `con` node, which holds a 3 byte `Uint8Array` with +the following octets: (1) the extension ID, (2) the session ID modulo 256, and +(3) the time sequence modulo 256. + +The extension *payload* is any JSON CRDT node with any value, which is specific +to the extension. + +``` +vec +├─ 0: con Uin8Array { , , } +└─ 1: any +``` From 60206908f8040bd23de5570ec5fddea3557dbce6 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 7 Mar 2024 08:27:44 +0100 Subject: [PATCH 2/9] =?UTF-8?q?docs(json-crdt-extensions):=20=E2=9C=8F?= =?UTF-8?q?=EF=B8=8F=20add=20extension=20view=20deom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mval/__demos__/view.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/json-crdt-extensions/mval/__demos__/view.ts diff --git a/src/json-crdt-extensions/mval/__demos__/view.ts b/src/json-crdt-extensions/mval/__demos__/view.ts new file mode 100644 index 0000000000..eed0494724 --- /dev/null +++ b/src/json-crdt-extensions/mval/__demos__/view.ts @@ -0,0 +1,28 @@ +/* tslint:disable no-console */ + +/** + * Run this demo with: + * + * npx nodemon -q -x ts-node src/json-crdt-extensions/mval/__demos__/view.ts + */ + +import {Model, s} from '../../../json-crdt'; +import {ValueMvExt} from '..'; + +console.clear(); + +const model = Model.withLogicalClock(1234); + +model.ext.register(ValueMvExt); + +model.api.root(ValueMvExt.new(s.con(1))); + +console.log(''); +console.log('Model with extension:'); +console.log(model + ''); + +const model2 = Model.fromBinary(model.toBinary()); + +console.log(''); +console.log('Model not aware of extension:'); +console.log(model2 + ''); From cc72152738652feac4c419b60482122188955161 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 7 Mar 2024 08:45:01 +0100 Subject: [PATCH 3/9] =?UTF-8?q?docs(json-crdt-extensions):=20=E2=9C=8F?= =?UTF-8?q?=EF=B8=8F=20improve=20mval=20demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-extensions/mval/__demos__/usage.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/json-crdt-extensions/mval/__demos__/usage.ts b/src/json-crdt-extensions/mval/__demos__/usage.ts index 638c5814d7..eda24b9862 100644 --- a/src/json-crdt-extensions/mval/__demos__/usage.ts +++ b/src/json-crdt-extensions/mval/__demos__/usage.ts @@ -17,7 +17,8 @@ model.ext.register(ValueMvExt); model.api.root({ obj: { - mv: ValueMvExt.new(s.con(1)), + name: s.con('John'), + score: ValueMvExt.new(s.con(1)), }, }); @@ -25,7 +26,7 @@ console.log(''); console.log('Initial value:'); console.log(model + ''); -const api = model.api.in(['obj', 'mv']).asExt(ValueMvExt); +const api = model.api.in(['obj', 'score']).asExt(ValueMvExt); api.set(s.con(5)); @@ -35,7 +36,7 @@ console.log(model + ''); const model2 = model.fork(); -const api2 = model2.api.in(['obj', 'mv']).asExt(ValueMvExt); +const api2 = model2.api.in(['obj', 'score']).asExt(ValueMvExt); api.set(s.con(10)); api2.set(s.con(20)); From 68434ccbecb47aca93c11bff23a983e94ced65f7 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 7 Mar 2024 09:14:31 +0100 Subject: [PATCH 4/9] =?UTF-8?q?refactor(json-crdt-extensions):=20?= =?UTF-8?q?=F0=9F=92=A1=20rename=20mval=20extension=20class=20to=20MvalExt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mval/__demos__/usage.ts | 10 +++--- .../mval/__demos__/view.ts | 6 ++-- .../{ValueMv.spec.ts => MvalExt.spec.ts} | 30 ++++++++-------- src/json-crdt-extensions/mval/index.ts | 2 +- src/json-crdt/extensions/README.md | 2 +- .../nodes/vec/__tests__/extension.spec.ts | 36 +++++++++---------- 6 files changed, 43 insertions(+), 43 deletions(-) rename src/json-crdt-extensions/mval/__tests__/{ValueMv.spec.ts => MvalExt.spec.ts} (74%) diff --git a/src/json-crdt-extensions/mval/__demos__/usage.ts b/src/json-crdt-extensions/mval/__demos__/usage.ts index eda24b9862..11d6b58cfc 100644 --- a/src/json-crdt-extensions/mval/__demos__/usage.ts +++ b/src/json-crdt-extensions/mval/__demos__/usage.ts @@ -7,18 +7,18 @@ */ import {Model, s} from '../../../json-crdt'; -import {ValueMvExt} from '..'; +import {MvalExt} from '..'; console.clear(); const model = Model.withLogicalClock(1234); -model.ext.register(ValueMvExt); +model.ext.register(MvalExt); model.api.root({ obj: { name: s.con('John'), - score: ValueMvExt.new(s.con(1)), + score: MvalExt.new(s.con(1)), }, }); @@ -26,7 +26,7 @@ console.log(''); console.log('Initial value:'); console.log(model + ''); -const api = model.api.in(['obj', 'score']).asExt(ValueMvExt); +const api = model.api.in(['obj', 'score']).asExt(MvalExt); api.set(s.con(5)); @@ -36,7 +36,7 @@ console.log(model + ''); const model2 = model.fork(); -const api2 = model2.api.in(['obj', 'score']).asExt(ValueMvExt); +const api2 = model2.api.in(['obj', 'score']).asExt(MvalExt); api.set(s.con(10)); api2.set(s.con(20)); diff --git a/src/json-crdt-extensions/mval/__demos__/view.ts b/src/json-crdt-extensions/mval/__demos__/view.ts index eed0494724..bd2f26f497 100644 --- a/src/json-crdt-extensions/mval/__demos__/view.ts +++ b/src/json-crdt-extensions/mval/__demos__/view.ts @@ -7,15 +7,15 @@ */ import {Model, s} from '../../../json-crdt'; -import {ValueMvExt} from '..'; +import {MvalExt} from '..'; console.clear(); const model = Model.withLogicalClock(1234); -model.ext.register(ValueMvExt); +model.ext.register(MvalExt); -model.api.root(ValueMvExt.new(s.con(1))); +model.api.root(MvalExt.new(s.con(1))); console.log(''); console.log('Model with extension:'); diff --git a/src/json-crdt-extensions/mval/__tests__/ValueMv.spec.ts b/src/json-crdt-extensions/mval/__tests__/MvalExt.spec.ts similarity index 74% rename from src/json-crdt-extensions/mval/__tests__/ValueMv.spec.ts rename to src/json-crdt-extensions/mval/__tests__/MvalExt.spec.ts index 97cffa0eb9..d94e8bc45e 100644 --- a/src/json-crdt-extensions/mval/__tests__/ValueMv.spec.ts +++ b/src/json-crdt-extensions/mval/__tests__/MvalExt.spec.ts @@ -1,14 +1,14 @@ -import {ValueMvExt} from '..'; +import {MvalExt} from '..'; import {Model} from '../../../json-crdt/model'; test('can set new values in single fork', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new(1), + mv: MvalExt.new(1), }); expect(model.view()).toEqual({mv: [1]}); - const register = model.api.in(['mv']).asExt(ValueMvExt); + const register = model.api.in(['mv']).asExt(MvalExt); register.set(2); expect(model.view()).toEqual({mv: [2]}); register.set(3); @@ -17,11 +17,11 @@ test('can set new values in single fork', () => { test('removes tombstones on insert', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new(1), + mv: MvalExt.new(1), }); - const register = model.api.in(['mv']).asExt(ValueMvExt); + const register = model.api.in(['mv']).asExt(MvalExt); expect(register.node.data.size()).toBe(1); register.set(2); expect(register.node.data.size()).toBe(1); @@ -33,13 +33,13 @@ test('removes tombstones on insert', () => { test('contains two values when two forks set value concurrently', () => { const model1 = Model.withLogicalClock(); - model1.ext.register(ValueMvExt); + model1.ext.register(MvalExt); model1.api.root({ - mv: ValueMvExt.new(1), + mv: MvalExt.new(1), }); const model2 = model1.fork(); - const register1 = model1.api.in(['mv']).asExt(ValueMvExt); - const register2 = model2.api.in(['mv']).asExt(ValueMvExt); + const register1 = model1.api.in(['mv']).asExt(MvalExt); + const register2 = model2.api.in(['mv']).asExt(MvalExt); register1.set(2); register2.set(3); expect(model1.view()).toEqual({mv: [2]}); @@ -56,13 +56,13 @@ test('contains two values when two forks set value concurrently', () => { test('contains one value when a fork overwrites a register', () => { const model1 = Model.withLogicalClock(); - model1.ext.register(ValueMvExt); + model1.ext.register(MvalExt); model1.api.root({ - mv: ValueMvExt.new(1), + mv: MvalExt.new(1), }); const model2 = model1.fork(); - const register1 = model1.api.in(['mv']).asExt(ValueMvExt); - const register2 = model2.api.in(['mv']).asExt(ValueMvExt); + const register1 = model1.api.in(['mv']).asExt(MvalExt); + const register2 = model2.api.in(['mv']).asExt(MvalExt); register1.set(2); register2.set(3); model1.applyPatch(model2.api.flush()); diff --git a/src/json-crdt-extensions/mval/index.ts b/src/json-crdt-extensions/mval/index.ts index 5e1c7a074e..b1e64acc9f 100644 --- a/src/json-crdt-extensions/mval/index.ts +++ b/src/json-crdt-extensions/mval/index.ts @@ -7,7 +7,7 @@ import type {ITimestampStruct} from '../../json-crdt-patch/clock'; import type {ArrNode} from '../../json-crdt/nodes/arr/ArrNode'; import type {ExtensionDefinition} from '../../json-crdt'; -export const ValueMvExt: ExtensionDefinition = { +export const MvalExt: ExtensionDefinition = { id: ExtensionId.mval, name: 'mval', new: (value: unknown | ITimestampStruct) => diff --git a/src/json-crdt/extensions/README.md b/src/json-crdt/extensions/README.md index f479a894d5..eee667078c 100644 --- a/src/json-crdt/extensions/README.md +++ b/src/json-crdt/extensions/README.md @@ -24,6 +24,6 @@ to the extension. ``` vec -├─ 0: con Uin8Array { , , } +├─ 0: con Uin8Array { , , } └─ 1: any ``` diff --git a/src/json-crdt/nodes/vec/__tests__/extension.spec.ts b/src/json-crdt/nodes/vec/__tests__/extension.spec.ts index 8e6ae482cc..d5f4a78ac7 100644 --- a/src/json-crdt/nodes/vec/__tests__/extension.spec.ts +++ b/src/json-crdt/nodes/vec/__tests__/extension.spec.ts @@ -1,16 +1,16 @@ -import {ValueMvExt} from '../../../../json-crdt-extensions/mval'; +import {MvalExt} from '../../../../json-crdt-extensions/mval'; import {konst} from '../../../../json-crdt-patch/builder/Konst'; import {Model} from '../../../../json-crdt/model'; test('can specify extension name', () => { - expect(ValueMvExt.name).toBe('mval'); + expect(MvalExt.name).toBe('mval'); }); test('can create a new multi-value register', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new(), + mv: MvalExt.new(), }); expect(model.view()).toEqual({ mv: [], @@ -19,9 +19,9 @@ test('can create a new multi-value register', () => { test('can provide initial value', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new({foo: 'bar'}), + mv: MvalExt.new({foo: 'bar'}), }); expect(model.view()).toEqual({ mv: [{foo: 'bar'}], @@ -30,22 +30,22 @@ test('can provide initial value', () => { test('can read view from node or API node', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new('foo'), + mv: MvalExt.new('foo'), }); - const api = model.api.in('mv').asExt(ValueMvExt); + const api = model.api.in('mv').asExt(MvalExt); expect(api.view()).toEqual(['foo']); expect(api.node.view()).toEqual(['foo']); }); test('exposes API to edit extension data', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new(), + mv: MvalExt.new(), }); - const nodeApi = model.api.in('mv').asExt(ValueMvExt); + const nodeApi = model.api.in('mv').asExt(MvalExt); nodeApi.set(konst('lol')); expect(model.view()).toEqual({ mv: ['lol'], @@ -55,9 +55,9 @@ test('exposes API to edit extension data', () => { describe('extension validity checks', () => { test('does not treat ArrNode as extension if header is too long', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new(), + mv: MvalExt.new(), }); const buf = new Uint8Array(4); buf.set(model.api.const(['mv', 0]).node.view() as Uint8Array, 0); @@ -70,9 +70,9 @@ describe('extension validity checks', () => { test('does not treat ArrNode as extension if header sid is wrong', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new(), + mv: MvalExt.new(), }); const buf = model.api.const(['mv', 0]).node.view() as Uint8Array; buf[1] += 1; @@ -84,9 +84,9 @@ describe('extension validity checks', () => { test('does not treat ArrNode as extension if header time is wrong', () => { const model = Model.withLogicalClock(); - model.ext.register(ValueMvExt); + model.ext.register(MvalExt); model.api.root({ - mv: ValueMvExt.new(), + mv: MvalExt.new(), }); const buf = model.api.const(['mv', 0]).node.view() as Uint8Array; buf[2] += 1; From d6e9af158681682f52c919ea7cd2040dddd82f38 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 7 Mar 2024 09:37:24 +0100 Subject: [PATCH 5/9] =?UTF-8?q?feat(json-crdt-extensions):=20=F0=9F=8E=B8?= =?UTF-8?q?=20export=20all=20extensions=20from=20the=20root=20level?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-extensions/index.ts | 2 ++ .../mval/__demos__/docs.ts | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/json-crdt-extensions/index.ts create mode 100644 src/json-crdt-extensions/mval/__demos__/docs.ts diff --git a/src/json-crdt-extensions/index.ts b/src/json-crdt-extensions/index.ts new file mode 100644 index 0000000000..745c3b1ab5 --- /dev/null +++ b/src/json-crdt-extensions/index.ts @@ -0,0 +1,2 @@ +export * from './mval'; +export * from './cnt'; diff --git a/src/json-crdt-extensions/mval/__demos__/docs.ts b/src/json-crdt-extensions/mval/__demos__/docs.ts new file mode 100644 index 0000000000..f6617d9494 --- /dev/null +++ b/src/json-crdt-extensions/mval/__demos__/docs.ts @@ -0,0 +1,29 @@ +/* tslint:disable no-console */ + +/** + * Run this demo with: + * + * npx nodemon -q -x ts-node src/json-crdt-extensions/mval/__demos__/docs.ts + */ + +import {Model, s} from '../../../json-crdt'; +import {MvalExt} from '..'; + +console.clear(); + +const model = Model.withLogicalClock(1234); + +model.ext.register(MvalExt); + +model.api.root({ + score: MvalExt.new(1), +}); + +const api = model.api.in(['score']).asExt(MvalExt); +const values = api.view(); + +console.log(values); + +api.set(2); + +console.log(model + ''); From ce7087d057e2ef2ec711b3d25db96de389cb98e9 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 7 Mar 2024 09:43:07 +0100 Subject: [PATCH 6/9] =?UTF-8?q?refactor(json-crdt):=20=F0=9F=92=A1=20print?= =?UTF-8?q?=20extensions=20with=20lower=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-extensions/mval/__demos__/docs.ts | 2 +- src/json-crdt/extensions/Extensions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json-crdt-extensions/mval/__demos__/docs.ts b/src/json-crdt-extensions/mval/__demos__/docs.ts index f6617d9494..ce5c321aea 100644 --- a/src/json-crdt-extensions/mval/__demos__/docs.ts +++ b/src/json-crdt-extensions/mval/__demos__/docs.ts @@ -24,6 +24,6 @@ const values = api.view(); console.log(values); -api.set(2); +api.set(s.con(2)); console.log(model + ''); diff --git a/src/json-crdt/extensions/Extensions.ts b/src/json-crdt/extensions/Extensions.ts index e3fd7e4ebb..65143f027b 100644 --- a/src/json-crdt/extensions/Extensions.ts +++ b/src/json-crdt/extensions/Extensions.ts @@ -28,7 +28,7 @@ export class Extensions implements Printable { .map((k) => +k) .sort(); return ( - this.constructor.name + + 'extensions' + printTree( tab, keys.map((k) => (tab) => `${k}: ${this.ext[k].name}`), From a042b9687a29fe8707185f8f89b52c854e614951 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 7 Mar 2024 09:47:55 +0100 Subject: [PATCH 7/9] =?UTF-8?q?docs(json-crdt-extensions):=20=E2=9C=8F?= =?UTF-8?q?=EF=B8=8F=20print=20model=20state=20after=20initialization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-extensions/mval/__demos__/docs.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/json-crdt-extensions/mval/__demos__/docs.ts b/src/json-crdt-extensions/mval/__demos__/docs.ts index ce5c321aea..5d75ffaf15 100644 --- a/src/json-crdt-extensions/mval/__demos__/docs.ts +++ b/src/json-crdt-extensions/mval/__demos__/docs.ts @@ -18,6 +18,8 @@ model.ext.register(MvalExt); model.api.root({ score: MvalExt.new(1), }); +console.log(model + ''); + const api = model.api.in(['score']).asExt(MvalExt); const values = api.view(); From 31fb8cc7fae72830165b9085cbd28cd679b9c100 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 7 Mar 2024 10:22:10 +0100 Subject: [PATCH 8/9] =?UTF-8?q?docs(json-crdt-extensions):=20=E2=9C=8F?= =?UTF-8?q?=EF=B8=8F=20add=20counter=20extension=20documentation=20demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cnt/__demos__/docs.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/json-crdt-extensions/cnt/__demos__/docs.ts diff --git a/src/json-crdt-extensions/cnt/__demos__/docs.ts b/src/json-crdt-extensions/cnt/__demos__/docs.ts new file mode 100644 index 0000000000..2e285a0265 --- /dev/null +++ b/src/json-crdt-extensions/cnt/__demos__/docs.ts @@ -0,0 +1,31 @@ +/* tslint:disable no-console */ + +/** + * Run this demo with: + * + * npx nodemon -q -x ts-node src/json-crdt-extensions/cnt/__demos__/docs.ts + */ + +import {Model, s} from '../../../json-crdt'; +import {CntExt} from '..'; + +console.clear(); + +const model = Model.withLogicalClock(1234); + +model.ext.register(CntExt); + +model.api.root({ + counter: CntExt.new(1), +}); +console.log(model + ''); + + +const api = model.api.in(['counter']).asExt(CntExt); +const values = api.view(); + +console.log(values); + +api.inc(10); + +console.log(model + ''); From f17efa27b5e078b9c495e003aa20351e7a3e5108 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 7 Mar 2024 11:01:16 +0100 Subject: [PATCH 9/9] =?UTF-8?q?style(json-crdt-extensions):=20=F0=9F=92=84?= =?UTF-8?q?=20run=20Prettier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-extensions/cnt/__demos__/docs.ts | 3 +++ src/json-crdt-extensions/mval/__demos__/docs.ts | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/json-crdt-extensions/cnt/__demos__/docs.ts b/src/json-crdt-extensions/cnt/__demos__/docs.ts index 2e285a0265..aff2ecb0cd 100644 --- a/src/json-crdt-extensions/cnt/__demos__/docs.ts +++ b/src/json-crdt-extensions/cnt/__demos__/docs.ts @@ -20,6 +20,9 @@ model.api.root({ }); console.log(model + ''); +// Excess use only ... +// 2-3 days for finding damages ... +// .. const api = model.api.in(['counter']).asExt(CntExt); const values = api.view(); diff --git a/src/json-crdt-extensions/mval/__demos__/docs.ts b/src/json-crdt-extensions/mval/__demos__/docs.ts index 5d75ffaf15..95d3d86463 100644 --- a/src/json-crdt-extensions/mval/__demos__/docs.ts +++ b/src/json-crdt-extensions/mval/__demos__/docs.ts @@ -20,7 +20,6 @@ model.api.root({ }); console.log(model + ''); - const api = model.api.in(['score']).asExt(MvalExt); const values = api.view();