From 58fe580dfe1d4fb7bf67d360c489e58b0e723297 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 10 Jun 2025 15:42:00 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20specify?= =?UTF-8?q?=20RGA=20chunk=20data=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/nodes/rga/AbstractRga.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/json-crdt/nodes/rga/AbstractRga.ts b/src/json-crdt/nodes/rga/AbstractRga.ts index b2b70957ee..44d940b9e5 100644 --- a/src/json-crdt/nodes/rga/AbstractRga.ts +++ b/src/json-crdt/nodes/rga/AbstractRga.ts @@ -17,10 +17,15 @@ import {printTree} from 'tree-dump/lib/printTree'; import {printBinary} from 'tree-dump/lib/printBinary'; import {printOctets} from '@jsonjoy.com/util/lib/buffers/printOctets'; +export interface ChunkData { + length: number; + slice: (start: number, end: number) => ChunkData; +} + /** * @category CRDT Node */ -export interface Chunk { +export interface Chunk> { /** Unique sortable ID of this chunk and its span. */ id: ITimestampStruct; /** Length of the logical clock interval of this chunk. */ @@ -56,35 +61,35 @@ export interface Chunk { /** Return a deep copy of itself. */ clone(): Chunk; /** Return the data of the chunk, if not deleted. */ - view(): T & {slice: (start: number, end: number) => T}; + view(): T; } -const compareById = (c1: Chunk, c2: Chunk): number => { +const compareById = (c1: Chunk>, c2: Chunk>): number => { const ts1 = c1.id; const ts2 = c2.id; return ts1.sid - ts2.sid || ts1.time - ts2.time; }; -const updateLenOne = (chunk: Chunk): void => { +const updateLenOne = (chunk: Chunk>): void => { const l = chunk.l; const r = chunk.r; chunk.len = (chunk.del ? 0 : chunk.span) + (l ? l.len : 0) + (r ? r.len : 0); }; -const updateLenOneLive = (chunk: Chunk): void => { +const updateLenOneLive = (chunk: Chunk>): void => { const l = chunk.l; const r = chunk.r; chunk.len = chunk.span + (l ? l.len : 0) + (r ? r.len : 0); }; -const dLen = (chunk: Chunk | undefined, delta: number): void => { +const dLen = (chunk: Chunk> | undefined, delta: number): void => { while (chunk) { chunk.len += delta; chunk = chunk.p; } }; -const next = (curr: Chunk): Chunk | undefined => { +const next = >(curr: Chunk): Chunk | undefined => { const r = curr.r; if (r) { curr = r; @@ -100,7 +105,7 @@ const next = (curr: Chunk): Chunk | undefined => { return p; }; -const prev = (curr: Chunk): Chunk | undefined => { +const prev = >(curr: Chunk): Chunk | undefined => { const l = curr.l; if (l) { curr = l; @@ -119,7 +124,7 @@ const prev = (curr: Chunk): Chunk | undefined => { /** * @category CRDT Node */ -export abstract class AbstractRga { +export abstract class AbstractRga> { public root: Chunk | undefined = undefined; public ids: Chunk | undefined = undefined; public count: number = 0; From 09142e9bdc625573c756d2753109e62ddf723040 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 10 Jun 2025 15:42:19 +0200 Subject: [PATCH 2/2] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20start=20s?= =?UTF-8?q?ample=20JSON=20CRDT=20mockup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/nodes/json/JsonNode.ts | 158 +++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/json-crdt/nodes/json/JsonNode.ts diff --git a/src/json-crdt/nodes/json/JsonNode.ts b/src/json-crdt/nodes/json/JsonNode.ts new file mode 100644 index 0000000000..10b30faf53 --- /dev/null +++ b/src/json-crdt/nodes/json/JsonNode.ts @@ -0,0 +1,158 @@ +import {AbstractRga, type Chunk} from '../rga/AbstractRga'; +import {type ITimestampStruct, tick} from '../../../json-crdt-patch/clock'; +import {printBinary} from 'tree-dump/lib/printBinary'; +import {printTree} from 'tree-dump/lib/printTree'; +import type {Model} from '../../model'; +import type {JsonNode, JsonNodeView} from '..'; +import type {Printable} from 'tree-dump/lib/types'; + + +class JsonToken {} +class JsonTextToken extends JsonToken {} +class JsonControlToken extends JsonToken {} +class JsonObjectStartToken extends JsonControlToken {} +class JsonObjectEntryDividerToken extends JsonControlToken {} +class JsonObjectEntryEndToken extends JsonControlToken {} +class JsonObjectEndToken extends JsonControlToken {} +class JsonCompositeToken extends JsonToken {} + +type Token = JsonTextToken | JsonObjectStartToken | JsonObjectEntryDividerToken | JsonObjectEntryEndToken | JsonObjectEndToken | JsonCompositeToken; + +class JsonChunkData { + public readonly data: Token[] = []; +} + +/** + * @ignore + * @category CRDT Node + */ +export class JsonChunk implements Chunk { + public readonly id: ITimestampStruct; + public span: number; + public del: boolean; + public data: JsonChunkData | undefined; + public len: number; + public p: JsonChunk | undefined; + public l: JsonChunk | undefined; + public r: JsonChunk | undefined; + public p2: JsonChunk | undefined; + public l2: JsonChunk | undefined; + public r2: JsonChunk | undefined; + public s: JsonChunk | undefined; + + constructor(id: ITimestampStruct, span: number, data: JsonChunkData | undefined) { + this.id = id; + this.span = span; + this.len = data ? span : 0; + this.del = !data; + this.p = undefined; + this.l = undefined; + this.r = undefined; + this.s = undefined; + this.data = data; + } + + public merge(data: JsonChunkData) { + throw new Error('not implemented'); + // this.data!.push(...data); + // this.span = this.data!.length; + } + + public split(ticks: number): JsonChunk { + throw new Error('not implemented'); + // const span = this.span; + // this.span = ticks; + // if (!this.del) { + // const data = this.data!; + // const rightData = data.splice(ticks); + // const chunk = new JsonChunk(tick(this.id, ticks), span - ticks, rightData); + // return chunk; + // } + // return new JsonChunk(tick(this.id, ticks), span - ticks, undefined); + } + + public delete(): void { + throw new Error('not implemented'); + // this.del = true; + // this.data = undefined; + } + + public clone(): JsonChunk { + throw new Error('not implemented'); + // return new JsonChunk(this.id, this.span, this.data ? [...this.data] : undefined); + } + + public view(): JsonChunkData { + throw new Error('not implemented'); + // return this.data ? [...this.data] : []; + } +} + +/** + * Represents the `arr` JSON CRDT type, which is a Replicated Growable Array + * (RGA). Each element ot the array is a reference to another JSON CRDT node. + * + * @category CRDT Node + */ +export class ArrNode + extends AbstractRga + implements JsonNode[]>, Printable +{ + constructor( + public readonly doc: Model, + id: ITimestampStruct, + ) { + super(id); + } + + // -------------------------------------------------------------- AbstractRga + + /** @ignore */ + public createChunk(id: ITimestampStruct, data: JsonChunkData | undefined): JsonChunk { + throw new Error('not implemented'); + // return new JsonChunk(id, data ? data.length : 0, data); + } + + /** @ignore */ + protected onChange(): void {} + + protected toStringName(): string { + return this.name(); + } + + // ----------------------------------------------------------------- JsonNode + + /** @ignore */ + public child() { + return undefined; + } + + /** @ignore */ + public container(): JsonNode | undefined { + return this; + } + + /** @ignore */ + private _view: unknown[] = []; + public view(): JsonNodeView[] { + throw new Error('not implemented'); + } + + /** @ignore */ + public children(callback: (node: JsonNode) => void) { + const index = this.doc.index; + for (let chunk = this.first(); chunk; chunk = this.next(chunk)) { + const data = chunk.data; + if (!data) continue; + const length = data.length; + for (let i = 0; i < length; i++) callback(index.get(data[i])!); + } + } + + /** @ignore */ + public api: undefined | unknown = undefined; + + public name(): string { + return 'arr'; + } +}