From b2e28f5de4640975fa68d84402ea11817225dd1e Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 13:41:49 +0100 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20a=20single=20?= =?UTF-8?q?automated=20asset=20publication=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index dd3741bdcd..bd728e941f 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "coverage": "yarn test --collectCoverage", "typedoc": "typedoc", "build:pages": "rimraf gh-pages && mkdir -p gh-pages && cp -r typedocs/* gh-pages && cp -r coverage gh-pages/coverage", - "deploy:pages": "gh-pages -d gh-pages" + "deploy:pages": "gh-pages -d gh-pages", + "publish-coverage-and-typedocs": "yarn typedoc && yarn coverage && yarn build:pages && yarn deploy:pages" }, "keywords": [], "peerDependencies": { From d6504893fbb072bb7ab95f2ff9985d460a7d4816 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 13:42:03 +0100 Subject: [PATCH 2/5] =?UTF-8?q?test(json-crdt):=20=F0=9F=92=8D=20add=20nod?= =?UTF-8?q?e=20recrusion=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-patch/PatchBuilder.ts | 1 + src/json-crdt/__tests__/recursion.spec.ts | 145 ++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 src/json-crdt/__tests__/recursion.spec.ts diff --git a/src/json-crdt-patch/PatchBuilder.ts b/src/json-crdt-patch/PatchBuilder.ts index ef61ed1527..5347110e94 100644 --- a/src/json-crdt-patch/PatchBuilder.ts +++ b/src/json-crdt-patch/PatchBuilder.ts @@ -210,6 +210,7 @@ export class PatchBuilder { * Set value of a "val" object. * * @returns ID of the new operation. + * @todo Rename to "insVal". */ public setVal(obj: ITimestampStruct, val: ITimestampStruct): ITimestampStruct { this.pad(); diff --git a/src/json-crdt/__tests__/recursion.spec.ts b/src/json-crdt/__tests__/recursion.spec.ts new file mode 100644 index 0000000000..87f8ac666c --- /dev/null +++ b/src/json-crdt/__tests__/recursion.spec.ts @@ -0,0 +1,145 @@ +import {Model} from '../model'; + +describe('recursive node references are not allowed', () => { + describe('arr', () => { + describe('ins_arr', () => { + test('reference to parent object in same patch', () => { + const model = Model.withLogicalClock(); + const builder = model.api.builder; + const objId = builder.obj(); + const arrId = builder.arr(); + builder.insObj(objId, [['arr', arrId]]); + builder.insArr(arrId, arrId, [builder.const(1), objId]); + builder.root(objId); + const patch1 = builder.flush(); + model.applyPatch(patch1); + expect(model.view()).toStrictEqual({arr: [1]}); + }); + + test('reference to parent object in second patch', () => { + const model = Model.withLogicalClock(); + const builder = model.api.builder; + const objId = builder.obj(); + const arrId = builder.arr(); + builder.insObj(objId, [['arr', arrId]]); + builder.insArr(arrId, arrId, [builder.const(1)]); + builder.root(objId); + const patch1 = builder.flush(); + model.applyPatch(patch1); + builder.insArr(arrId, arrId, [objId]); + const patch2 = builder.flush(); + model.applyPatch(patch2); + expect(model.view()).toStrictEqual({arr: [1]}); + }); + }); + }); + + describe('obj', () => { + describe('ins_obj', () => { + test('reference to object self in own key', () => { + const model = Model.withLogicalClock(); + const builder = model.api.builder; + const objId = builder.obj(); + builder.insObj(objId, [ + ['con', builder.const(2)], + ['obj', objId], + ]); + builder.root(objId); + const patch1 = builder.flush(); + model.applyPatch(patch1); + expect(model.view()).toStrictEqual({con: 2}); + }); + + test('reference to object self in own key in separate patch', () => { + const model = Model.withLogicalClock(); + const builder = model.api.builder; + const objId = builder.obj(); + builder.insObj(objId, [ + ['con', builder.const(2)], + ]); + builder.root(objId); + const patch1 = builder.flush(); + model.applyPatch(patch1); + builder.insObj(objId, [ + ['obj', objId], + ]); + const patch2 = builder.flush(); + model.applyPatch(patch2); + expect(model.view()).toStrictEqual({con: 2}); + }); + }); + }); + + describe('vec', () => { + describe('ins_vec', () => { + test('reference to parent object in same patch', () => { + const model = Model.withLogicalClock(); + const builder = model.api.builder; + const vecId = builder.vec(); + builder.insVec(vecId, [ + [0, builder.const(1)], + [1, vecId], + ]); + builder.root(vecId); + const patch1 = builder.flush(); + model.applyPatch(patch1); + expect(model.view()).toStrictEqual([1]); + }); + + test('reference to parent object in second patch', () => { + const model = Model.withLogicalClock(); + const builder = model.api.builder; + const vecId = builder.vec(); + builder.insVec(vecId, [ + [0, builder.const(1)], + [1, vecId], + ]); + builder.root(vecId); + const patch1 = builder.flush(); + model.applyPatch(patch1); + builder.insVec(vecId, [ + [1, vecId], + ]); + const patch2 = builder.flush(); + model.applyPatch(patch2); + expect(model.view()).toStrictEqual([1]); + }); + }); + }); + + describe('val', () => { + describe('new_val', () => { + test('reference to parent object in same patch', () => { + const model = Model.withLogicalClock(); + const builder = model.api.builder; + const objId = builder.obj(); + const valId = builder.val(); + builder.insObj(objId, [['val', valId]]); + builder.root(objId); + const patch1 = builder.flush(); + model.applyPatch(patch1); + console.log(model + ''); + console.log(model.view()); + expect(model.view()).toStrictEqual({val: undefined}); + }); + }); + + describe('ins_val', () => { + test('reference to parent object in second patch', () => { + const model = Model.withLogicalClock(); + const builder = model.api.builder; + const objId = builder.obj(); + const conId = builder.const(3); + const valId = builder.val(conId); + builder.insObj(objId, [['val', valId]]); + builder.root(objId); + const patch1 = builder.flush(); + model.applyPatch(patch1); + builder.setVal(valId, objId); + const patch2 = builder.flush(); + model.applyPatch(patch2); + expect(model.view()).toStrictEqual({val: 3}); + }); + }); + }); +}); \ No newline at end of file From 8ab4e2c70417c269841e5743951ea21ec06435e6 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 14:06:21 +0100 Subject: [PATCH 3/5] =?UTF-8?q?fix(json-crdt-patch):=20=F0=9F=90=9B=20prev?= =?UTF-8?q?ent=20recursion=20through=20new=5Fval=20operation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-patch/Patch.ts | 78 ++++++++----------- src/json-crdt-patch/PatchBuilder.ts | 9 ++- src/json-crdt-patch/builder/schema.ts | 4 +- .../codec/__tests__/PatchFuzzer.ts | 2 +- src/json-crdt-patch/codec/binary/Decoder.ts | 2 +- src/json-crdt-patch/codec/binary/Encoder.ts | 3 - src/json-crdt-patch/codec/compact/decode.ts | 2 +- src/json-crdt-patch/codec/compact/encode.ts | 2 +- src/json-crdt-patch/codec/verbose/decode.ts | 2 +- src/json-crdt-patch/codec/verbose/encode.ts | 2 +- src/json-crdt-patch/codec/verbose/types.ts | 7 +- src/json-crdt-patch/operations.ts | 5 +- src/json-crdt/__tests__/recursion.spec.ts | 22 +----- src/json-crdt/model/Model.ts | 5 +- .../model/__tests__/Model.value.spec.ts | 24 ++++-- src/json-crdt/nodes/root/RootNode.ts | 8 +- src/json-crdt/nodes/val/ValNode.ts | 4 +- 17 files changed, 75 insertions(+), 106 deletions(-) diff --git a/src/json-crdt-patch/Patch.ts b/src/json-crdt-patch/Patch.ts index 0e7974c31b..b370b6f308 100644 --- a/src/json-crdt-patch/Patch.ts +++ b/src/json-crdt-patch/Patch.ts @@ -1,20 +1,4 @@ -import { - NewConOp, - NewObjOp, - NewValOp, - NewVecOp, - NewStrOp, - NewBinOp, - NewArrOp, - InsValOp, - InsObjOp, - InsVecOp, - InsStrOp, - InsBinOp, - InsArrOp, - DelOp, - NopOp, -} from './operations'; +import * as operations from './operations'; import {ITimestampStruct, ts, toDisplayString} from './clock'; import {SESSION} from './constants'; import {encode, decode} from './codec/binary'; @@ -24,21 +8,21 @@ import type {Printable} from '../util/print/types'; * A union type of all possible JSON CRDT patch operations. */ export type JsonCrdtPatchOperation = - | NewConOp - | NewValOp - | NewVecOp - | NewObjOp - | NewStrOp - | NewBinOp - | NewArrOp - | InsValOp - | InsObjOp - | InsVecOp - | InsStrOp - | InsBinOp - | InsArrOp - | DelOp - | NopOp; + | operations.NewConOp + | operations.NewValOp + | operations.NewVecOp + | operations.NewObjOp + | operations.NewStrOp + | operations.NewBinOp + | operations.NewArrOp + | operations.InsValOp + | operations.InsObjOp + | operations.InsVecOp + | operations.InsStrOp + | operations.InsBinOp + | operations.InsArrOp + | operations.DelOp + | operations.NopOp; /** * Represents a JSON CRDT patch. @@ -135,27 +119,27 @@ export class Patch implements Printable { const patchOps = patch.ops; for (let i = 0; i < length; i++) { const op = ops[i]; - if (op instanceof DelOp) patchOps.push(new DelOp(ts(op.id), ts(op.obj), op.what)); - else if (op instanceof NewConOp) patchOps.push(new NewConOp(ts(op.id), op.val)); - else if (op instanceof NewVecOp) patchOps.push(new NewVecOp(ts(op.id))); - else if (op instanceof NewValOp) patchOps.push(new NewValOp(ts(op.id), ts(op.val))); - else if (op instanceof NewObjOp) patchOps.push(new NewObjOp(ts(op.id))); - else if (op instanceof NewStrOp) patchOps.push(new NewStrOp(ts(op.id))); - else if (op instanceof NewBinOp) patchOps.push(new NewBinOp(ts(op.id))); - else if (op instanceof NewArrOp) patchOps.push(new NewArrOp(ts(op.id))); - else if (op instanceof InsArrOp) patchOps.push(new InsArrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data.map(ts))); - else if (op instanceof InsStrOp) patchOps.push(new InsStrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data)); - else if (op instanceof InsBinOp) patchOps.push(new InsBinOp(ts(op.id), ts(op.obj), ts(op.ref), op.data)); - else if (op instanceof InsValOp) patchOps.push(new InsValOp(ts(op.id), ts(op.obj), ts(op.val))); - else if (op instanceof InsObjOp) + if (op instanceof operations.DelOp) patchOps.push(new operations.DelOp(ts(op.id), ts(op.obj), op.what)); + else if (op instanceof operations.NewConOp) patchOps.push(new operations.NewConOp(ts(op.id), op.val)); + else if (op instanceof operations.NewVecOp) patchOps.push(new operations.NewVecOp(ts(op.id))); + else if (op instanceof operations.NewValOp) patchOps.push(new operations.NewValOp(ts(op.id))); + else if (op instanceof operations.NewObjOp) patchOps.push(new operations.NewObjOp(ts(op.id))); + else if (op instanceof operations.NewStrOp) patchOps.push(new operations.NewStrOp(ts(op.id))); + else if (op instanceof operations.NewBinOp) patchOps.push(new operations.NewBinOp(ts(op.id))); + else if (op instanceof operations.NewArrOp) patchOps.push(new operations.NewArrOp(ts(op.id))); + else if (op instanceof operations.InsArrOp) patchOps.push(new operations.InsArrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data.map(ts))); + else if (op instanceof operations.InsStrOp) patchOps.push(new operations.InsStrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data)); + else if (op instanceof operations.InsBinOp) patchOps.push(new operations.InsBinOp(ts(op.id), ts(op.obj), ts(op.ref), op.data)); + else if (op instanceof operations.InsValOp) patchOps.push(new operations.InsValOp(ts(op.id), ts(op.obj), ts(op.val))); + else if (op instanceof operations.InsObjOp) patchOps.push( - new InsObjOp( + new operations.InsObjOp( ts(op.id), ts(op.obj), op.data.map(([key, value]) => [key, ts(value)]), ), ); - else if (op instanceof NopOp) patchOps.push(new NopOp(ts(op.id), op.len)); + else if (op instanceof operations.NopOp) patchOps.push(new operations.NopOp(ts(op.id), op.len)); } return patch; } diff --git a/src/json-crdt-patch/PatchBuilder.ts b/src/json-crdt-patch/PatchBuilder.ts index 5347110e94..57b0a5ce15 100644 --- a/src/json-crdt-patch/PatchBuilder.ts +++ b/src/json-crdt-patch/PatchBuilder.ts @@ -154,11 +154,12 @@ export class PatchBuilder { * * @param val Reference to another object. * @returns ID of the new operation. + * @todo Rename to `newVal`. */ - public val(val: ITimestampStruct): ITimestampStruct { + public val(): ITimestampStruct { this.pad(); const id = this.clock.tick(1); - this.patch.ops.push(new NewValOp(id, val)); + this.patch.ops.push(new NewValOp(id)); return id; } @@ -351,7 +352,9 @@ export class PatchBuilder { */ public jsonVal(value: unknown): ITimestampStruct { const id = this.const(value); - return this.val(id); + const valId = this.val(); + this.setVal(valId, id); + return valId; } /** diff --git a/src/json-crdt-patch/builder/schema.ts b/src/json-crdt-patch/builder/schema.ts index 47a6fed246..cee1faa71a 100644 --- a/src/json-crdt-patch/builder/schema.ts +++ b/src/json-crdt-patch/builder/schema.ts @@ -32,8 +32,10 @@ export namespace nodes { constructor(public readonly value: T) { super((builder) => { + const valId = builder.val(); const valueId = value.build(builder); - return builder.val(valueId); + builder.setVal(valId, valueId); + return valId; }); } } diff --git a/src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts b/src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts index b0419a9f0b..7ae8cab852 100644 --- a/src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts +++ b/src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts @@ -28,7 +28,7 @@ export class PatchFuzzer extends Fuzzer { () => builder.arr(), () => builder.str(), () => builder.bin(), - () => builder.val(ts()), + () => builder.val(), () => builder.const(RandomJson.generate()), () => builder.root(ts()), () => diff --git a/src/json-crdt-patch/codec/binary/Decoder.ts b/src/json-crdt-patch/codec/binary/Decoder.ts index b07a78c3be..39a49dd91d 100644 --- a/src/json-crdt-patch/codec/binary/Decoder.ts +++ b/src/json-crdt-patch/codec/binary/Decoder.ts @@ -73,7 +73,7 @@ export class Decoder extends CborDecoder { break; } case JsonCrdtPatchOpcode.new_val: { - builder.val(this.decodeId()); + builder.val(); break; } case JsonCrdtPatchOpcode.new_obj: { diff --git a/src/json-crdt-patch/codec/binary/Encoder.ts b/src/json-crdt-patch/codec/binary/Encoder.ts index dc9bb5a7c7..1ebe3cab7f 100644 --- a/src/json-crdt-patch/codec/binary/Encoder.ts +++ b/src/json-crdt-patch/codec/binary/Encoder.ts @@ -94,10 +94,7 @@ export class Encoder extends CborEncoder { break; } case operations.NewValOp: { - const operation = op; - const val = operation.val; writer.u8(JsonCrdtPatchOpcode.new_val); - this.encodeId(val); break; } case operations.NewObjOp: { diff --git a/src/json-crdt-patch/codec/compact/decode.ts b/src/json-crdt-patch/codec/compact/decode.ts index 072cba09ab..cad04d79bf 100644 --- a/src/json-crdt-patch/codec/compact/decode.ts +++ b/src/json-crdt-patch/codec/compact/decode.ts @@ -38,7 +38,7 @@ export const decode = (data: types.CompactCodecPatch): Patch => { break; } case JsonCrdtPatchOpcode.new_val: { - builder.val(timestamp(sid, op[1])); + builder.val(); break; } case JsonCrdtPatchOpcode.new_obj: { diff --git a/src/json-crdt-patch/codec/compact/encode.ts b/src/json-crdt-patch/codec/compact/encode.ts index d3f1390f06..03486f6c40 100644 --- a/src/json-crdt-patch/codec/compact/encode.ts +++ b/src/json-crdt-patch/codec/compact/encode.ts @@ -47,7 +47,7 @@ export const encode = (patch: Patch): types.CompactCodecPatch => { res.push([JsonCrdtPatchOpcode.new_con, val]); } } else if (op instanceof operations.NewValOp) { - res.push([JsonCrdtPatchOpcode.new_val, timestamp(sid, op.val)]); + res.push([JsonCrdtPatchOpcode.new_val]); } else if (op instanceof operations.NewObjOp) { res.push([JsonCrdtPatchOpcode.new_obj]); } else if (op instanceof operations.NewVecOp) { diff --git a/src/json-crdt-patch/codec/verbose/decode.ts b/src/json-crdt-patch/codec/verbose/decode.ts index 4bfd962813..49a4831eac 100644 --- a/src/json-crdt-patch/codec/verbose/decode.ts +++ b/src/json-crdt-patch/codec/verbose/decode.ts @@ -30,7 +30,7 @@ export const decode = (data: types.JsonCodecPatch): Patch => { break; } case 'new_val': { - builder.val(decodeId(op.value)); + builder.val(); break; } case 'new_obj': { diff --git a/src/json-crdt-patch/codec/verbose/encode.ts b/src/json-crdt-patch/codec/verbose/encode.ts index 422be1e255..e169b813c8 100644 --- a/src/json-crdt-patch/codec/verbose/encode.ts +++ b/src/json-crdt-patch/codec/verbose/encode.ts @@ -45,7 +45,7 @@ export const encode = (patch: Patch): types.JsonCodecPatch => { } else if (op instanceof operations.NewBinOp) { ops.push({op: 'new_bin'}); } else if (op instanceof operations.NewValOp) { - ops.push({op: 'new_val', value: encodeTimestamp(op.val)}); + ops.push({op: 'new_val'}); } else if (op instanceof operations.InsObjOp) { ops.push({ op: 'ins_obj', diff --git a/src/json-crdt-patch/codec/verbose/types.ts b/src/json-crdt-patch/codec/verbose/types.ts index 0ab56a9886..3554a3cb27 100644 --- a/src/json-crdt-patch/codec/verbose/types.ts +++ b/src/json-crdt-patch/codec/verbose/types.ts @@ -119,12 +119,7 @@ export interface JsonCodecNewConOperation extends JsonCodecOperationBase<'new_co * Operation which creates a new "val" CRDT data type, which is a * Last-Write-Wins Register of a pointer to another CRDT data type. */ -export interface JsonCodecNewValOperation extends JsonCodecOperationBase<'new_val'> { - /** - * ID of the "val" LWW-Register object latest value. - */ - value: JsonCodecTimestamp; -} +export type JsonCodecNewValOperation = JsonCodecOperationBase<'new_val'>; /** * Operation which creates a new "object" CRDT data type, which is a map of diff --git a/src/json-crdt-patch/operations.ts b/src/json-crdt-patch/operations.ts index f63b91e386..a405793b48 100644 --- a/src/json-crdt-patch/operations.ts +++ b/src/json-crdt-patch/operations.ts @@ -37,7 +37,7 @@ export class NewConOp implements IJsonCrdtPatchOperation { * @category Operations */ export class NewValOp implements IJsonCrdtPatchOperation { - constructor(public readonly id: ITimestampStruct, public readonly val: ITimestampStruct) {} + constructor(public readonly id: ITimestampStruct) {} public span(): number { return 1; @@ -48,7 +48,7 @@ export class NewValOp implements IJsonCrdtPatchOperation { } public toString(): string { - return `"${this.name()}" ${toDisplayString(this.id)} { ${toDisplayString(this.val)} }`; + return `"${this.name()}" ${toDisplayString(this.id)}`; } } @@ -165,6 +165,7 @@ export class NewArrOp implements IJsonCrdtPatchOperation { export class InsValOp implements IJsonCrdtPatchEditOperation { constructor( public readonly id: ITimestampStruct, + /** @todo Rename to `node`. */ public readonly obj: ITimestampStruct, public readonly val: ITimestampStruct, ) {} diff --git a/src/json-crdt/__tests__/recursion.spec.ts b/src/json-crdt/__tests__/recursion.spec.ts index 87f8ac666c..ccb485bf55 100644 --- a/src/json-crdt/__tests__/recursion.spec.ts +++ b/src/json-crdt/__tests__/recursion.spec.ts @@ -108,37 +108,21 @@ describe('recursive node references are not allowed', () => { }); describe('val', () => { - describe('new_val', () => { - test('reference to parent object in same patch', () => { - const model = Model.withLogicalClock(); - const builder = model.api.builder; - const objId = builder.obj(); - const valId = builder.val(); - builder.insObj(objId, [['val', valId]]); - builder.root(objId); - const patch1 = builder.flush(); - model.applyPatch(patch1); - console.log(model + ''); - console.log(model.view()); - expect(model.view()).toStrictEqual({val: undefined}); - }); - }); - describe('ins_val', () => { test('reference to parent object in second patch', () => { const model = Model.withLogicalClock(); const builder = model.api.builder; const objId = builder.obj(); - const conId = builder.const(3); - const valId = builder.val(conId); + const valId = builder.val(); builder.insObj(objId, [['val', valId]]); builder.root(objId); const patch1 = builder.flush(); model.applyPatch(patch1); + expect((model.view() as any).val).toStrictEqual(undefined); builder.setVal(valId, objId); const patch2 = builder.flush(); model.applyPatch(patch2); - expect(model.view()).toStrictEqual({val: 3}); + expect((model.view() as any).val).toStrictEqual(undefined); }); }); }); diff --git a/src/json-crdt/model/Model.ts b/src/json-crdt/model/Model.ts index a663c58138..c7ab454497 100644 --- a/src/json-crdt/model/Model.ts +++ b/src/json-crdt/model/Model.ts @@ -187,10 +187,7 @@ export class Model implements Printabl if (!index.get(id)) index.set(id, new StrNode(id)); } else if (op instanceof operations.NewValOp) { const id = op.id; - if (!index.get(id)) { - const val = index.get(op.val); - if (val) index.set(id, new ValNode(this, id, op.val)); - } + if (!index.get(id)) index.set(id, new ValNode(this, id, ORIGIN)); } else if (op instanceof operations.NewConOp) { const id = op.id; if (!index.get(id)) index.set(id, new ConNode(id, op.val)); diff --git a/src/json-crdt/model/__tests__/Model.value.spec.ts b/src/json-crdt/model/__tests__/Model.value.spec.ts index 6029e768da..ca6d2d8850 100644 --- a/src/json-crdt/model/__tests__/Model.value.spec.ts +++ b/src/json-crdt/model/__tests__/Model.value.spec.ts @@ -7,8 +7,9 @@ describe('Document', () => { test('can create a value', () => { const doc = Model.withLogicalClock(); const builder = new PatchBuilder(doc.clock); + const numId = builder.val(); const val = builder.const([1, 2, 3]); - const numId = builder.val(val); + builder.setVal(numId, val); doc.applyPatch(builder.patch); const obj = doc.index.get(numId); expect(obj).toBeInstanceOf(ValNode); @@ -17,7 +18,8 @@ describe('Document', () => { test('can set value as document root', () => { const doc = Model.withLogicalClock(); const builder = new PatchBuilder(doc.clock); - const numId = builder.val(builder.const(10_000)); + const numId = builder.val(); + builder.setVal(numId, builder.const(10_000)); builder.root(numId); doc.applyPatch(builder.patch); expect(doc.view()).toEqual(10_000); @@ -26,7 +28,8 @@ describe('Document', () => { test('can update value to a number', () => { const doc = Model.withLogicalClock(); const builder = new PatchBuilder(doc.clock); - const objId = builder.val(builder.const(1)); + const objId = builder.val(); + builder.setVal(objId, builder.const(1)); builder.setVal(objId, builder.const(2)); builder.root(objId); doc.applyPatch(builder.patch); @@ -36,7 +39,8 @@ describe('Document', () => { test('can update value to a string', () => { const doc = Model.withLogicalClock(); const builder = new PatchBuilder(doc.clock); - const objId = builder.val(builder.const(1)); + const objId = builder.val(); + builder.setVal(objId, builder.const(1)); builder.setVal(objId, builder.const('boom')); builder.root(objId); doc.applyPatch(builder.patch); @@ -46,7 +50,8 @@ describe('Document', () => { test('can overwrite number value', () => { const doc = Model.withLogicalClock(); const builder = new PatchBuilder(doc.clock); - const valId = builder.val(builder.const(-1)); + const valId = builder.val(); + builder.setVal(valId, builder.const(-1)); builder.setVal(valId, builder.const(123)); builder.setVal(valId, builder.const(5.5)); builder.root(valId); @@ -58,7 +63,8 @@ describe('Document', () => { const doc = Model.withLogicalClock(); const builder = new PatchBuilder(doc.clock); const objId = builder.obj(); - const valId = builder.val(builder.const(25)); + const valId = builder.val(); + builder.setVal(valId, builder.const(25)); builder.insObj(objId, [['gg', valId]]); builder.setVal(valId, builder.const(123)); builder.setVal(valId, builder.const(99)); @@ -71,7 +77,8 @@ describe('Document', () => { const doc = Model.withLogicalClock(); const builder = new PatchBuilder(doc.clock); const objId = builder.obj(); - const valId = builder.val(builder.const(25)); + const valId = builder.val(); + builder.setVal(valId, builder.const(25)); builder.insObj(objId, [['gg', valId]]); builder.setVal(valId, builder.const(123)); builder.setVal(valId, builder.const(true)); @@ -84,7 +91,8 @@ describe('Document', () => { const doc = Model.withLogicalClock(); const builder = new PatchBuilder(doc.clock); const objId = builder.arr(); - const valId = builder.val(builder.const(25)); + const valId = builder.val(); + builder.setVal(valId, builder.const(25)); builder.insArr(objId, objId, [valId]); builder.setVal(valId, builder.const(123)); builder.setVal(valId, builder.const(true)); diff --git a/src/json-crdt/nodes/root/RootNode.ts b/src/json-crdt/nodes/root/RootNode.ts index 89882d1340..2ade5f44b9 100644 --- a/src/json-crdt/nodes/root/RootNode.ts +++ b/src/json-crdt/nodes/root/RootNode.ts @@ -1,6 +1,6 @@ -import {ORIGIN, SESSION} from '../../../json-crdt-patch/constants'; +import {ORIGIN} from '../../../json-crdt-patch/constants'; import {ValNode} from '../val/ValNode'; -import {Model, UNDEFINED} from '../../model/Model'; +import {Model} from '../../model/Model'; import type {ITimestampStruct} from '../../../json-crdt-patch/clock'; import type {JsonNode} from '../types'; @@ -18,8 +18,4 @@ export class RootNode extends ValNode constructor(doc: Model, val: ITimestampStruct) { super(doc, ORIGIN, val); } - - public node(): Value { - return this.val.sid === SESSION.SYSTEM ? UNDEFINED : super.node(); - } } diff --git a/src/json-crdt/nodes/val/ValNode.ts b/src/json-crdt/nodes/val/ValNode.ts index 6761c286d8..7b63226888 100644 --- a/src/json-crdt/nodes/val/ValNode.ts +++ b/src/json-crdt/nodes/val/ValNode.ts @@ -1,6 +1,7 @@ import {compare, ITimestampStruct, toDisplayString} from '../../../json-crdt-patch/clock'; import {SESSION} from '../../../json-crdt-patch/constants'; import {printTree} from '../../../util/print/printTree'; +import {UNDEFINED} from '../../model/Model'; import type {JsonNode, JsonNodeView} from '..'; import type {Model} from '../../model'; import type {Printable} from '../../../util/print/types'; @@ -31,6 +32,7 @@ export class ValNode implements JsonNode implements JsonNodeUNDEFINED : this.child()!; } // ----------------------------------------------------------------- JsonNode From 9aa33f109baed04e70d269d380b236a401e28b1e Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 14:16:55 +0100 Subject: [PATCH 4/5] =?UTF-8?q?fix(json-crdt-patch):=20=F0=9F=90=9B=20make?= =?UTF-8?q?=20all=20tests=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-patch/PatchBuilder.ts | 2 +- src/json-crdt-patch/codec/verbose/__tests__/bug-1.spec.ts | 2 -- src/json-crdt/model/__tests__/Model.server-clock.spec.ts | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/json-crdt-patch/PatchBuilder.ts b/src/json-crdt-patch/PatchBuilder.ts index 57b0a5ce15..6da120f103 100644 --- a/src/json-crdt-patch/PatchBuilder.ts +++ b/src/json-crdt-patch/PatchBuilder.ts @@ -351,8 +351,8 @@ export class PatchBuilder { * Run builder commands to create a JSON value. */ public jsonVal(value: unknown): ITimestampStruct { - const id = this.const(value); const valId = this.val(); + const id = this.const(value); this.setVal(valId, id); return valId; } diff --git a/src/json-crdt-patch/codec/verbose/__tests__/bug-1.spec.ts b/src/json-crdt-patch/codec/verbose/__tests__/bug-1.spec.ts index 63c343d372..577ea1f6d2 100644 --- a/src/json-crdt-patch/codec/verbose/__tests__/bug-1.spec.ts +++ b/src/json-crdt-patch/codec/verbose/__tests__/bug-1.spec.ts @@ -9,7 +9,6 @@ const encoded1 = { }, { op: 'new_val', - value: 1, }, { op: 'new_bin', @@ -22,7 +21,6 @@ const encoded1 = { }, { op: 'new_val', - value: 2, }, { op: 'ins_obj', diff --git a/src/json-crdt/model/__tests__/Model.server-clock.spec.ts b/src/json-crdt/model/__tests__/Model.server-clock.spec.ts index 179df3d426..25bef11d38 100644 --- a/src/json-crdt/model/__tests__/Model.server-clock.spec.ts +++ b/src/json-crdt/model/__tests__/Model.server-clock.spec.ts @@ -7,7 +7,7 @@ describe('server clock', () => { const model = Model.withServerClock(); expect(model.clock.time).toBe(1); model.api.root(true); - expect(model.clock.time).toBe(4); + expect(model.clock.time).toBe(5); model.api.root({foo: 'bar'}); model.applyPatch(model.api.builder.patch); model.applyPatch(model.api.builder.patch); @@ -18,7 +18,7 @@ describe('server clock', () => { const model = Model.withServerClock(); expect(model.clock.time).toBe(1); model.api.root(true); - expect(model.clock.time).toBe(4); + expect(model.clock.time).toBe(5); const clock = model.clock.clone(); clock.tick(1); const builder = new PatchBuilder(clock); From d67bf34a45315a2f0aa7aaf0088a07c1050f5e37 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 14:18:52 +0100 Subject: [PATCH 5/5] =?UTF-8?q?style:=20=F0=9F=92=84=20run=20Prettier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-patch/Patch.ts | 12 ++++++++---- src/json-crdt/__tests__/recursion.spec.ts | 14 ++++---------- src/json-crdt/nodes/val/ValNode.ts | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/json-crdt-patch/Patch.ts b/src/json-crdt-patch/Patch.ts index b370b6f308..a7b667e560 100644 --- a/src/json-crdt-patch/Patch.ts +++ b/src/json-crdt-patch/Patch.ts @@ -127,10 +127,14 @@ export class Patch implements Printable { else if (op instanceof operations.NewStrOp) patchOps.push(new operations.NewStrOp(ts(op.id))); else if (op instanceof operations.NewBinOp) patchOps.push(new operations.NewBinOp(ts(op.id))); else if (op instanceof operations.NewArrOp) patchOps.push(new operations.NewArrOp(ts(op.id))); - else if (op instanceof operations.InsArrOp) patchOps.push(new operations.InsArrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data.map(ts))); - else if (op instanceof operations.InsStrOp) patchOps.push(new operations.InsStrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data)); - else if (op instanceof operations.InsBinOp) patchOps.push(new operations.InsBinOp(ts(op.id), ts(op.obj), ts(op.ref), op.data)); - else if (op instanceof operations.InsValOp) patchOps.push(new operations.InsValOp(ts(op.id), ts(op.obj), ts(op.val))); + else if (op instanceof operations.InsArrOp) + patchOps.push(new operations.InsArrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data.map(ts))); + else if (op instanceof operations.InsStrOp) + patchOps.push(new operations.InsStrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data)); + else if (op instanceof operations.InsBinOp) + patchOps.push(new operations.InsBinOp(ts(op.id), ts(op.obj), ts(op.ref), op.data)); + else if (op instanceof operations.InsValOp) + patchOps.push(new operations.InsValOp(ts(op.id), ts(op.obj), ts(op.val))); else if (op instanceof operations.InsObjOp) patchOps.push( new operations.InsObjOp( diff --git a/src/json-crdt/__tests__/recursion.spec.ts b/src/json-crdt/__tests__/recursion.spec.ts index ccb485bf55..9fdc3f68e3 100644 --- a/src/json-crdt/__tests__/recursion.spec.ts +++ b/src/json-crdt/__tests__/recursion.spec.ts @@ -54,15 +54,11 @@ describe('recursive node references are not allowed', () => { const model = Model.withLogicalClock(); const builder = model.api.builder; const objId = builder.obj(); - builder.insObj(objId, [ - ['con', builder.const(2)], - ]); + builder.insObj(objId, [['con', builder.const(2)]]); builder.root(objId); const patch1 = builder.flush(); model.applyPatch(patch1); - builder.insObj(objId, [ - ['obj', objId], - ]); + builder.insObj(objId, [['obj', objId]]); const patch2 = builder.flush(); model.applyPatch(patch2); expect(model.view()).toStrictEqual({con: 2}); @@ -97,9 +93,7 @@ describe('recursive node references are not allowed', () => { builder.root(vecId); const patch1 = builder.flush(); model.applyPatch(patch1); - builder.insVec(vecId, [ - [1, vecId], - ]); + builder.insVec(vecId, [[1, vecId]]); const patch2 = builder.flush(); model.applyPatch(patch2); expect(model.view()).toStrictEqual([1]); @@ -126,4 +120,4 @@ describe('recursive node references are not allowed', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/src/json-crdt/nodes/val/ValNode.ts b/src/json-crdt/nodes/val/ValNode.ts index 7b63226888..23dc8186a9 100644 --- a/src/json-crdt/nodes/val/ValNode.ts +++ b/src/json-crdt/nodes/val/ValNode.ts @@ -32,7 +32,7 @@ export class ValNode implements JsonNode