From 648dd56aa06218309d37a4d963916cc84fd0a16c Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 29 Mar 2024 19:33:24 +0100 Subject: [PATCH 1/8] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20implement?= =?UTF-8?q?=20abstract=20undo/redo=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/history/UndoRedoStack.ts | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/json-crdt/history/UndoRedoStack.ts diff --git a/src/json-crdt/history/UndoRedoStack.ts b/src/json-crdt/history/UndoRedoStack.ts new file mode 100644 index 0000000000..526e8f1234 --- /dev/null +++ b/src/json-crdt/history/UndoRedoStack.ts @@ -0,0 +1,41 @@ +export interface UndoItem { + undo(): RedoItem; +} + +export interface RedoItem { + redo(): UndoItem; +} + +export class UndoRedoStack { + private undoStack: UndoItem[] = []; + private redoStack: RedoItem[] = []; + + public undoLength(): number { + return this.undoStack.length; + } + + public redoLength(): number { + return this.redoStack.length; + } + + public push(undo: UndoItem): RedoItem[] { + const redoStack = this.redoStack; + this.redoStack = []; + this.undoStack.push(undo); + return redoStack; + } + + public undo(): void { + const undo = this.undoStack.pop(); + if (!undo) return; + const redo = undo.undo(); + this.redoStack.push(redo); + } + + public redo(): void { + const redo = this.redoStack.pop(); + if (!redo) return; + const undo = redo.redo(); + this.undoStack.push(undo); + } +} From eaf8e44fab3304912981b30be65b137a3bbc95fe Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 29 Mar 2024 20:49:25 +0100 Subject: [PATCH 2/8] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20implement?= =?UTF-8?q?=20toSchema()=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/model/api/ModelApi.ts | 2 + .../schema/__tests__/toSchema.spec.ts | 78 +++++++++++++++++++ src/json-crdt/schema/toSchema.ts | 27 +++++++ 3 files changed, 107 insertions(+) create mode 100644 src/json-crdt/schema/__tests__/toSchema.spec.ts create mode 100644 src/json-crdt/schema/toSchema.ts diff --git a/src/json-crdt/model/api/ModelApi.ts b/src/json-crdt/model/api/ModelApi.ts index c84170098f..c18722648d 100644 --- a/src/json-crdt/model/api/ModelApi.ts +++ b/src/json-crdt/model/api/ModelApi.ts @@ -191,6 +191,8 @@ export class ModelApi implements SyncStore { + if (a instanceof nodes.con && b instanceof nodes.con) return deepEqual(a.raw, b.raw); + else if (a instanceof nodes.val && b instanceof nodes.val) return cmp(a.value, b.value); + else if (a instanceof nodes.obj && b instanceof nodes.obj) { + const objAKeys = Object.keys(a.obj); + const objBKeys = Object.keys(a.obj); + const objALen = objAKeys.length; + const objBLen = objBKeys.length; + if (objALen !== objBLen) return false; + const optAKeys = Object.keys(a.opt || {}); + const optBKeys = Object.keys(b.opt || {}); + const optALen = optAKeys.length; + const optBLen = optBKeys.length; + if (optALen !== optBLen) return false; + for (let i = 0; i < objALen; i++) { + const key = objAKeys[i]; + if (!cmp(a.obj[key], b.obj[key])) return false; + } + for (let i = 0; i < optALen; i++) { + const key = optAKeys[i]; + if (!cmp(a.opt![key], b.opt![key])) return false; + } + return true; + } else if (a instanceof nodes.vec && b instanceof nodes.vec) { + const vecA = a.value; + const vecB = b.value; + const len = vecA.length; + if (len !== vecB.length) return false; + for (let i = 0; i < len; i++) if (!cmp(vecA[i], vecA[i])) return false; + return true; + } else if (a instanceof nodes.str && b instanceof nodes.str) return a.raw === b.raw; + else if (a instanceof nodes.bin && b instanceof nodes.bin) return cmpUint8Array(a.raw, b.raw); + else if (a instanceof nodes.arr && b instanceof nodes.arr) { + const arrA = a.arr; + const arrB = b.arr; + const len = arrA.length; + if (len !== arrB.length) return false; + for (let i = 0; i < len; i++) if (!cmp(arrA[i], arrB[i])) return false; + return true; + } + return false; +}; + +test('can infer schema of a document nodes', () => { + const con = s.con('con'); + const str = s.str('hello'); + const obj = s.obj({ + id: s.con('id'), + val: s.val(s.str('world')), + }); + const schema = s.obj({ + con, + str, + bin: s.bin(new Uint8Array([1, 2, 3])), + obj, + vec: s.vec(s.con(1), s.con({foo: 'bar'})), + arr: s.arr([s.con(1), s.con({foo: 'bar'})]), + }); + const model = Model.withLogicalClock().setSchema(schema); + const node = model.root.node(); + const schema2 = toSchema(node); + expect(cmp(schema, schema2)).toBe(true); + const conSchema = toSchema(model.api.const('con').node); + expect(cmp(con, conSchema)).toBe(true); + expect(cmp(str, conSchema)).toBe(false); + const strSchema = toSchema(model.api.str('str').node); + expect(cmp(str, strSchema)).toBe(true); + expect(cmp(con, strSchema)).toBe(false); + const objSchema = toSchema(model.api.obj('obj').node); + expect(cmp(obj, objSchema)).toBe(true); + expect(cmp(con, objSchema)).toBe(false); +}); diff --git a/src/json-crdt/schema/toSchema.ts b/src/json-crdt/schema/toSchema.ts new file mode 100644 index 0000000000..648511489a --- /dev/null +++ b/src/json-crdt/schema/toSchema.ts @@ -0,0 +1,27 @@ +import {JsonNode, ConNode, ValNode, ObjNode, VecNode, StrNode, BinNode, ArrNode} from "../nodes"; +import {NodeBuilder, s} from "../../json-crdt-patch"; + +export const toSchema = (node: JsonNode): NodeBuilder => { + if (node instanceof ConNode) return s.con(node.val); + if (node instanceof ValNode) return s.val(toSchema(node.node())); + if (node instanceof ObjNode) { + const obj: Record = {}; + node.nodes((child, key) => obj[key] = toSchema(child)); + return s.obj(obj); + } + if (node instanceof VecNode) { + const arr: NodeBuilder[] = []; + node.children((child) => arr.push(toSchema(child))); + return s.vec(...arr); + } + if (node instanceof StrNode) return s.str(node.view()); + if (node instanceof BinNode) return s.bin(node.view()); + if (node instanceof ArrNode) { + const arr: NodeBuilder[] = []; + node.children((child) => { + if (child) arr.push(toSchema(child)); + }); + return s.arr(arr); + } + return s.con(undefined); +}; From 410ce43dc02a671c61097a61bcf048b1a3391ccb Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 29 Mar 2024 20:51:52 +0100 Subject: [PATCH 3/8] =?UTF-8?q?docs(json-crdt):=20=E2=9C=8F=EF=B8=8F=20add?= =?UTF-8?q?=20toSchema()=20JSDoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/schema/toSchema.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/json-crdt/schema/toSchema.ts b/src/json-crdt/schema/toSchema.ts index 648511489a..8ae53cf248 100644 --- a/src/json-crdt/schema/toSchema.ts +++ b/src/json-crdt/schema/toSchema.ts @@ -1,6 +1,16 @@ import {JsonNode, ConNode, ValNode, ObjNode, VecNode, StrNode, BinNode, ArrNode} from "../nodes"; import {NodeBuilder, s} from "../../json-crdt-patch"; +/** + * Converts any JSON CRDT node to a schema representation. The schema can be + * used to copy the structure of the JSON CRDT node to another document or + * another location in the same document. + * + * @todo Add type generic. + * + * @param node JSON CRDT node to recursively convert to schema. + * @returns Schema representation of the JSON CRDT node. + */ export const toSchema = (node: JsonNode): NodeBuilder => { if (node instanceof ConNode) return s.con(node.val); if (node instanceof ValNode) return s.val(toSchema(node.node())); From 0bb20ad74b1fb7f5531f8fb4a98036199d192add Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 29 Mar 2024 20:57:13 +0100 Subject: [PATCH 4/8] =?UTF-8?q?refactor(json-crdt):=20=F0=9F=92=A1=20renam?= =?UTF-8?q?e=20schema=20inference=20utility=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/model/Model.ts | 4 ++-- src/json-crdt/nodes/types.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/json-crdt/model/Model.ts b/src/json-crdt/model/Model.ts index 0d772ee943..018a2c0d46 100644 --- a/src/json-crdt/model/Model.ts +++ b/src/json-crdt/model/Model.ts @@ -6,7 +6,7 @@ import {JsonCrdtPatchOperation, Patch} from '../../json-crdt-patch/Patch'; import {ModelApi} from './api/ModelApi'; import {ORIGIN, SESSION, SYSTEM_SESSION_TIME} from '../../json-crdt-patch/constants'; import {randomSessionId} from './util'; -import {RootNode, ValNode, VecNode, ObjNode, StrNode, BinNode, ArrNode, BuilderNodeToJsonNode} from '../nodes'; +import {RootNode, ValNode, VecNode, ObjNode, StrNode, BinNode, ArrNode, SchemaToJsonNode} from '../nodes'; import {printTree} from '../../util/print/printTree'; import {Extensions} from '../extensions/Extensions'; import {AvlMap} from '../../util/trees/avl/AvlMap'; @@ -378,7 +378,7 @@ export class Model> implements Printable { * @param schema The schema to set for this model. * @returns Strictly typed model. */ - public setSchema(schema: S): Model> { + public setSchema(schema: S): Model> { if (this.clock.time < 2) this.api.root(schema); return this; } diff --git a/src/json-crdt/nodes/types.ts b/src/json-crdt/nodes/types.ts index 6afd077f44..f3119e910f 100644 --- a/src/json-crdt/nodes/types.ts +++ b/src/json-crdt/nodes/types.ts @@ -49,18 +49,18 @@ export interface JsonNode extends Identifiable { export type JsonNodeView = N extends JsonNode ? V : {[K in keyof N]: JsonNodeView}; // prettier-ignore -export type BuilderNodeToJsonNode = S extends builder.str +export type SchemaToJsonNode = S extends builder.str ? nodes.StrNode : S extends builder.bin ? nodes.BinNode : S extends builder.con ? nodes.ConNode : S extends builder.val - ? nodes.ValNode> + ? nodes.ValNode> : S extends builder.vec - ? nodes.VecNode<{[K in keyof T]: BuilderNodeToJsonNode}> + ? nodes.VecNode<{[K in keyof T]: SchemaToJsonNode}> : S extends builder.obj - ? nodes.ObjNode<{[K in keyof T]: BuilderNodeToJsonNode}> + ? nodes.ObjNode<{[K in keyof T]: SchemaToJsonNode}> : S extends builder.arr - ? nodes.ArrNode> + ? nodes.ArrNode> : JsonNode; From 6d478b81e34d64fd8d0b3d581a9c53b095bfd908 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 29 Mar 2024 21:04:23 +0100 Subject: [PATCH 5/8] =?UTF-8?q?refactor(json-crdt):=20=F0=9F=92=A1=20move?= =?UTF-8?q?=20SchemaToJsonNode=20type=20to=20/schema=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/model/Model.ts | 3 ++- src/json-crdt/nodes/types.ts | 19 ------------------- src/json-crdt/schema/types.ts | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 src/json-crdt/schema/types.ts diff --git a/src/json-crdt/model/Model.ts b/src/json-crdt/model/Model.ts index 018a2c0d46..6dbcd1872d 100644 --- a/src/json-crdt/model/Model.ts +++ b/src/json-crdt/model/Model.ts @@ -6,7 +6,8 @@ import {JsonCrdtPatchOperation, Patch} from '../../json-crdt-patch/Patch'; import {ModelApi} from './api/ModelApi'; import {ORIGIN, SESSION, SYSTEM_SESSION_TIME} from '../../json-crdt-patch/constants'; import {randomSessionId} from './util'; -import {RootNode, ValNode, VecNode, ObjNode, StrNode, BinNode, ArrNode, SchemaToJsonNode} from '../nodes'; +import {RootNode, ValNode, VecNode, ObjNode, StrNode, BinNode, ArrNode} from '../nodes'; +import {SchemaToJsonNode} from '../schema/types'; import {printTree} from '../../util/print/printTree'; import {Extensions} from '../extensions/Extensions'; import {AvlMap} from '../../util/trees/avl/AvlMap'; diff --git a/src/json-crdt/nodes/types.ts b/src/json-crdt/nodes/types.ts index f3119e910f..fb56284524 100644 --- a/src/json-crdt/nodes/types.ts +++ b/src/json-crdt/nodes/types.ts @@ -1,5 +1,3 @@ -import type {nodes as builder} from '../../json-crdt-patch'; -import type * as nodes from './nodes'; import type {Identifiable} from '../../json-crdt-patch/types'; /** @@ -47,20 +45,3 @@ export interface JsonNode extends Identifiable { } export type JsonNodeView = N extends JsonNode ? V : {[K in keyof N]: JsonNodeView}; - -// prettier-ignore -export type SchemaToJsonNode = S extends builder.str - ? nodes.StrNode - : S extends builder.bin - ? nodes.BinNode - : S extends builder.con - ? nodes.ConNode - : S extends builder.val - ? nodes.ValNode> - : S extends builder.vec - ? nodes.VecNode<{[K in keyof T]: SchemaToJsonNode}> - : S extends builder.obj - ? nodes.ObjNode<{[K in keyof T]: SchemaToJsonNode}> - : S extends builder.arr - ? nodes.ArrNode> - : JsonNode; diff --git a/src/json-crdt/schema/types.ts b/src/json-crdt/schema/types.ts new file mode 100644 index 0000000000..89a02ee743 --- /dev/null +++ b/src/json-crdt/schema/types.ts @@ -0,0 +1,19 @@ +import type {nodes as builder} from '../../json-crdt-patch'; +import type * as nodes from '../nodes'; + +// prettier-ignore +export type SchemaToJsonNode = S extends builder.str + ? nodes.StrNode + : S extends builder.bin + ? nodes.BinNode + : S extends builder.con + ? nodes.ConNode + : S extends builder.val + ? nodes.ValNode> + : S extends builder.vec + ? nodes.VecNode<{[K in keyof T]: SchemaToJsonNode}> + : S extends builder.obj + ? nodes.ObjNode<{[K in keyof T]: SchemaToJsonNode}> + : S extends builder.arr + ? nodes.ArrNode> + : nodes.JsonNode; From 1a3ec666ddcdc6292550642b054205a6bf925c9c Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 29 Mar 2024 21:35:13 +0100 Subject: [PATCH 6/8] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20add=20typ?= =?UTF-8?q?e=20which=20infers=20schema=20from=20JSON=20CRDT=20nodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/schema/__tests__/types.spec.ts | 55 ++++++++++++++++++++ src/json-crdt/schema/types.ts | 17 ++++++ 2 files changed, 72 insertions(+) create mode 100644 src/json-crdt/schema/__tests__/types.spec.ts diff --git a/src/json-crdt/schema/__tests__/types.spec.ts b/src/json-crdt/schema/__tests__/types.spec.ts new file mode 100644 index 0000000000..501c053a0b --- /dev/null +++ b/src/json-crdt/schema/__tests__/types.spec.ts @@ -0,0 +1,55 @@ +import {s} from '../../../json-crdt-patch'; +import {Model} from '../../model'; +import {JsonNodeToSchema, SchemaToJsonNode} from '../types'; + +describe('can infer schema of JSON CRDT nodes', () => { + test('con', () => { + const schema1 = s.con(123); + const schema2: JsonNodeToSchema> = schema1; + }); + + test('val', () => { + const schema1 = s.val(s.con(true)); + const schema2: JsonNodeToSchema> = schema1; + }); + + test('obj', () => { + const schema1 = s.obj({ + hello: s.con('world'), + }); + const schema2: JsonNodeToSchema> = schema1; + }); + + test('vec', () => { + const schema1 = s.vec(s.con(1), s.val(s.con(2))); + const schema2: JsonNodeToSchema> = schema1; + }); + + test('str', () => { + const schema1 = s.str('asdf'); + const schema2: JsonNodeToSchema> = schema1; + }); + + test('bin', () => { + const schema1 = s.bin(new Uint8Array([1, 2, 3])); + const schema2: JsonNodeToSchema> = schema1; + }); + + test('arr', () => { + const schema1 = s.arr([s.con(1), s.val(s.con(2))]); + const schema2: JsonNodeToSchema> = schema1; + }); + + test('from typed model', () => { + const model = Model.withLogicalClock().setSchema(s.obj({ + id: s.con('asdf'), + age: s.val(s.con(42)), + })); + type Node = ReturnType; + type Schema = JsonNodeToSchema; + const schema: Schema = s.obj({ + id: s.con('asdf'), + age: s.val(s.con(42)), + }); + }); +}); diff --git a/src/json-crdt/schema/types.ts b/src/json-crdt/schema/types.ts index 89a02ee743..01928ae3c9 100644 --- a/src/json-crdt/schema/types.ts +++ b/src/json-crdt/schema/types.ts @@ -17,3 +17,20 @@ export type SchemaToJsonNode = S extends builder.str : S extends builder.arr ? nodes.ArrNode> : nodes.JsonNode; + +// prettier-ignore +export type JsonNodeToSchema = N extends nodes.StrNode + ? builder.str + : N extends nodes.BinNode + ? builder.bin + : N extends nodes.ConNode + ? builder.con + : N extends nodes.ValNode + ? builder.val> + : N extends nodes.VecNode + ? builder.vec<{[K in keyof T]: JsonNodeToSchema}> + : N extends nodes.ObjNode + ? builder.obj<{[K in keyof T]: JsonNodeToSchema}> + : N extends nodes.ArrNode + ? builder.arr> + : builder.con; From 6f35c0117ff106036fffd624fd48a228ac5e1a67 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 29 Mar 2024 21:39:40 +0100 Subject: [PATCH 7/8] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20add=20inn?= =?UTF-8?q?er=20type=20safety=20to=20toSchema()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schema/__tests__/toSchema.spec.ts | 11 +++++++++++ src/json-crdt/schema/toSchema.ts | 19 ++++++++++--------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/json-crdt/schema/__tests__/toSchema.spec.ts b/src/json-crdt/schema/__tests__/toSchema.spec.ts index 8e03109a50..4850337d4f 100644 --- a/src/json-crdt/schema/__tests__/toSchema.spec.ts +++ b/src/json-crdt/schema/__tests__/toSchema.spec.ts @@ -76,3 +76,14 @@ test('can infer schema of a document nodes', () => { expect(cmp(obj, objSchema)).toBe(true); expect(cmp(con, objSchema)).toBe(false); }); + +test('can infer schema of a typed model', () => { + const schema = s.obj({ + id: s.con('id'), + val: s.val(s.str('world')), + }); + const model = Model.withLogicalClock().setSchema(schema); + const schema2 = toSchema(model.root.node()); + expect(schema2.obj.id).toBeInstanceOf(nodes.con); + expect(schema2.obj.val).toBeInstanceOf(nodes.val); +}); diff --git a/src/json-crdt/schema/toSchema.ts b/src/json-crdt/schema/toSchema.ts index 8ae53cf248..9064d57da0 100644 --- a/src/json-crdt/schema/toSchema.ts +++ b/src/json-crdt/schema/toSchema.ts @@ -1,5 +1,6 @@ import {JsonNode, ConNode, ValNode, ObjNode, VecNode, StrNode, BinNode, ArrNode} from "../nodes"; import {NodeBuilder, s} from "../../json-crdt-patch"; +import type {JsonNodeToSchema} from "./types"; /** * Converts any JSON CRDT node to a schema representation. The schema can be @@ -11,27 +12,27 @@ import {NodeBuilder, s} from "../../json-crdt-patch"; * @param node JSON CRDT node to recursively convert to schema. * @returns Schema representation of the JSON CRDT node. */ -export const toSchema = (node: JsonNode): NodeBuilder => { - if (node instanceof ConNode) return s.con(node.val); - if (node instanceof ValNode) return s.val(toSchema(node.node())); +export const toSchema = >(node: N): JsonNodeToSchema => { + if (node instanceof ConNode) return s.con(node.val) as any; + if (node instanceof ValNode) return s.val(toSchema(node.node())) as any; if (node instanceof ObjNode) { const obj: Record = {}; node.nodes((child, key) => obj[key] = toSchema(child)); - return s.obj(obj); + return s.obj(obj) as any; } if (node instanceof VecNode) { const arr: NodeBuilder[] = []; node.children((child) => arr.push(toSchema(child))); - return s.vec(...arr); + return s.vec(...arr) as any; } - if (node instanceof StrNode) return s.str(node.view()); - if (node instanceof BinNode) return s.bin(node.view()); + if (node instanceof StrNode) return s.str(node.view()) as any; + if (node instanceof BinNode) return s.bin(node.view()) as any; if (node instanceof ArrNode) { const arr: NodeBuilder[] = []; node.children((child) => { if (child) arr.push(toSchema(child)); }); - return s.arr(arr); + return s.arr(arr) as any; } - return s.con(undefined); + return s.con(undefined) as any; }; From 0723a095530b6b2a877de6fb8895dbcc3de1dbcb Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 29 Mar 2024 21:40:52 +0100 Subject: [PATCH 8/8] =?UTF-8?q?style(json-crdt):=20=F0=9F=92=84=20run=20Pr?= =?UTF-8?q?ettier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/model/api/ModelApi.ts | 2 +- src/json-crdt/schema/__tests__/types.spec.ts | 10 ++++++---- src/json-crdt/schema/toSchema.ts | 12 +++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/json-crdt/model/api/ModelApi.ts b/src/json-crdt/model/api/ModelApi.ts index c18722648d..c636536fe2 100644 --- a/src/json-crdt/model/api/ModelApi.ts +++ b/src/json-crdt/model/api/ModelApi.ts @@ -191,7 +191,7 @@ export class ModelApi implements SyncStore { }); test('from typed model', () => { - const model = Model.withLogicalClock().setSchema(s.obj({ - id: s.con('asdf'), - age: s.val(s.con(42)), - })); + const model = Model.withLogicalClock().setSchema( + s.obj({ + id: s.con('asdf'), + age: s.val(s.con(42)), + }), + ); type Node = ReturnType; type Schema = JsonNodeToSchema; const schema: Schema = s.obj({ diff --git a/src/json-crdt/schema/toSchema.ts b/src/json-crdt/schema/toSchema.ts index 9064d57da0..c21cb1faf5 100644 --- a/src/json-crdt/schema/toSchema.ts +++ b/src/json-crdt/schema/toSchema.ts @@ -1,14 +1,12 @@ -import {JsonNode, ConNode, ValNode, ObjNode, VecNode, StrNode, BinNode, ArrNode} from "../nodes"; -import {NodeBuilder, s} from "../../json-crdt-patch"; -import type {JsonNodeToSchema} from "./types"; +import {JsonNode, ConNode, ValNode, ObjNode, VecNode, StrNode, BinNode, ArrNode} from '../nodes'; +import {NodeBuilder, s} from '../../json-crdt-patch'; +import type {JsonNodeToSchema} from './types'; /** * Converts any JSON CRDT node to a schema representation. The schema can be * used to copy the structure of the JSON CRDT node to another document or * another location in the same document. - * - * @todo Add type generic. - * + * * @param node JSON CRDT node to recursively convert to schema. * @returns Schema representation of the JSON CRDT node. */ @@ -17,7 +15,7 @@ export const toSchema = >(node: N): JsonNodeToSchema if (node instanceof ValNode) return s.val(toSchema(node.node())) as any; if (node instanceof ObjNode) { const obj: Record = {}; - node.nodes((child, key) => obj[key] = toSchema(child)); + node.nodes((child, key) => (obj[key] = toSchema(child))); return s.obj(obj) as any; } if (node instanceof VecNode) {