Skip to content

Commit 8829f98

Browse files
authored
Merge pull request #559 from streamich/revert
Revert patches, support undo/redo stacks
2 parents 0a1e5be + 0723a09 commit 8829f98

File tree

8 files changed

+264
-21
lines changed

8 files changed

+264
-21
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export interface UndoItem {
2+
undo(): RedoItem;
3+
}
4+
5+
export interface RedoItem {
6+
redo(): UndoItem;
7+
}
8+
9+
export class UndoRedoStack {
10+
private undoStack: UndoItem[] = [];
11+
private redoStack: RedoItem[] = [];
12+
13+
public undoLength(): number {
14+
return this.undoStack.length;
15+
}
16+
17+
public redoLength(): number {
18+
return this.redoStack.length;
19+
}
20+
21+
public push(undo: UndoItem): RedoItem[] {
22+
const redoStack = this.redoStack;
23+
this.redoStack = [];
24+
this.undoStack.push(undo);
25+
return redoStack;
26+
}
27+
28+
public undo(): void {
29+
const undo = this.undoStack.pop();
30+
if (!undo) return;
31+
const redo = undo.undo();
32+
this.redoStack.push(redo);
33+
}
34+
35+
public redo(): void {
36+
const redo = this.redoStack.pop();
37+
if (!redo) return;
38+
const undo = redo.redo();
39+
this.undoStack.push(undo);
40+
}
41+
}

src/json-crdt/model/Model.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {JsonCrdtPatchOperation, Patch} from '../../json-crdt-patch/Patch';
66
import {ModelApi} from './api/ModelApi';
77
import {ORIGIN, SESSION, SYSTEM_SESSION_TIME} from '../../json-crdt-patch/constants';
88
import {randomSessionId} from './util';
9-
import {RootNode, ValNode, VecNode, ObjNode, StrNode, BinNode, ArrNode, BuilderNodeToJsonNode} from '../nodes';
9+
import {RootNode, ValNode, VecNode, ObjNode, StrNode, BinNode, ArrNode} from '../nodes';
10+
import {SchemaToJsonNode} from '../schema/types';
1011
import {printTree} from '../../util/print/printTree';
1112
import {Extensions} from '../extensions/Extensions';
1213
import {AvlMap} from '../../util/trees/avl/AvlMap';
@@ -378,7 +379,7 @@ export class Model<N extends JsonNode = JsonNode<any>> implements Printable {
378379
* @param schema The schema to set for this model.
379380
* @returns Strictly typed model.
380381
*/
381-
public setSchema<S extends NodeBuilder>(schema: S): Model<BuilderNodeToJsonNode<S>> {
382+
public setSchema<S extends NodeBuilder>(schema: S): Model<SchemaToJsonNode<S>> {
382383
if (this.clock.time < 2) this.api.root(schema);
383384
return <any>this;
384385
}

src/json-crdt/model/api/ModelApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ export class ModelApi<N extends JsonNode = JsonNode> implements SyncStore<JsonNo
192192
* Locates a `con` node and returns a local changes API for it. If the node
193193
* doesn't exist or the node at the path is not a `con` node, throws an error.
194194
*
195+
* @todo Rename to `con`.
196+
*
195197
* @param path Path at which to locate a node.
196198
* @returns A local changes API for a `con` node.
197199
*/

src/json-crdt/nodes/types.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import type {nodes as builder} from '../../json-crdt-patch';
2-
import type * as nodes from './nodes';
31
import type {Identifiable} from '../../json-crdt-patch/types';
42

53
/**
@@ -47,20 +45,3 @@ export interface JsonNode<View = unknown> extends Identifiable {
4745
}
4846

4947
export type JsonNodeView<N> = N extends JsonNode<infer V> ? V : {[K in keyof N]: JsonNodeView<N[K]>};
50-
51-
// prettier-ignore
52-
export type BuilderNodeToJsonNode<S> = S extends builder.str<infer T>
53-
? nodes.StrNode<T>
54-
: S extends builder.bin
55-
? nodes.BinNode
56-
: S extends builder.con<infer T>
57-
? nodes.ConNode<T>
58-
: S extends builder.val<infer T>
59-
? nodes.ValNode<BuilderNodeToJsonNode<T>>
60-
: S extends builder.vec<infer T>
61-
? nodes.VecNode<{[K in keyof T]: BuilderNodeToJsonNode<T[K]>}>
62-
: S extends builder.obj<infer T>
63-
? nodes.ObjNode<{[K in keyof T]: BuilderNodeToJsonNode<T[K]>}>
64-
: S extends builder.arr<infer T>
65-
? nodes.ArrNode<BuilderNodeToJsonNode<T>>
66-
: JsonNode;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {NodeBuilder, s, nodes} from '../../../json-crdt-patch';
2+
import {deepEqual} from '../../../json-equal/deepEqual';
3+
import {cmpUint8Array} from '../../../util/buffers/cmpUint8Array';
4+
import {Model} from '../../model';
5+
import {toSchema} from '../toSchema';
6+
7+
const cmp = (a: NodeBuilder, b: NodeBuilder): boolean => {
8+
if (a instanceof nodes.con && b instanceof nodes.con) return deepEqual(a.raw, b.raw);
9+
else if (a instanceof nodes.val && b instanceof nodes.val) return cmp(a.value, b.value);
10+
else if (a instanceof nodes.obj && b instanceof nodes.obj) {
11+
const objAKeys = Object.keys(a.obj);
12+
const objBKeys = Object.keys(a.obj);
13+
const objALen = objAKeys.length;
14+
const objBLen = objBKeys.length;
15+
if (objALen !== objBLen) return false;
16+
const optAKeys = Object.keys(a.opt || {});
17+
const optBKeys = Object.keys(b.opt || {});
18+
const optALen = optAKeys.length;
19+
const optBLen = optBKeys.length;
20+
if (optALen !== optBLen) return false;
21+
for (let i = 0; i < objALen; i++) {
22+
const key = objAKeys[i];
23+
if (!cmp(a.obj[key], b.obj[key])) return false;
24+
}
25+
for (let i = 0; i < optALen; i++) {
26+
const key = optAKeys[i];
27+
if (!cmp(a.opt![key], b.opt![key])) return false;
28+
}
29+
return true;
30+
} else if (a instanceof nodes.vec && b instanceof nodes.vec) {
31+
const vecA = a.value;
32+
const vecB = b.value;
33+
const len = vecA.length;
34+
if (len !== vecB.length) return false;
35+
for (let i = 0; i < len; i++) if (!cmp(vecA[i], vecA[i])) return false;
36+
return true;
37+
} else if (a instanceof nodes.str && b instanceof nodes.str) return a.raw === b.raw;
38+
else if (a instanceof nodes.bin && b instanceof nodes.bin) return cmpUint8Array(a.raw, b.raw);
39+
else if (a instanceof nodes.arr && b instanceof nodes.arr) {
40+
const arrA = a.arr;
41+
const arrB = b.arr;
42+
const len = arrA.length;
43+
if (len !== arrB.length) return false;
44+
for (let i = 0; i < len; i++) if (!cmp(arrA[i], arrB[i])) return false;
45+
return true;
46+
}
47+
return false;
48+
};
49+
50+
test('can infer schema of a document nodes', () => {
51+
const con = s.con('con');
52+
const str = s.str('hello');
53+
const obj = s.obj({
54+
id: s.con('id'),
55+
val: s.val(s.str('world')),
56+
});
57+
const schema = s.obj({
58+
con,
59+
str,
60+
bin: s.bin(new Uint8Array([1, 2, 3])),
61+
obj,
62+
vec: s.vec(s.con(1), s.con({foo: 'bar'})),
63+
arr: s.arr([s.con(1), s.con({foo: 'bar'})]),
64+
});
65+
const model = Model.withLogicalClock().setSchema(schema);
66+
const node = model.root.node();
67+
const schema2 = toSchema(node);
68+
expect(cmp(schema, schema2)).toBe(true);
69+
const conSchema = toSchema(model.api.const('con').node);
70+
expect(cmp(con, conSchema)).toBe(true);
71+
expect(cmp(str, conSchema)).toBe(false);
72+
const strSchema = toSchema(model.api.str('str').node);
73+
expect(cmp(str, strSchema)).toBe(true);
74+
expect(cmp(con, strSchema)).toBe(false);
75+
const objSchema = toSchema(model.api.obj('obj').node);
76+
expect(cmp(obj, objSchema)).toBe(true);
77+
expect(cmp(con, objSchema)).toBe(false);
78+
});
79+
80+
test('can infer schema of a typed model', () => {
81+
const schema = s.obj({
82+
id: s.con('id'),
83+
val: s.val(s.str('world')),
84+
});
85+
const model = Model.withLogicalClock().setSchema(schema);
86+
const schema2 = toSchema(model.root.node());
87+
expect(schema2.obj.id).toBeInstanceOf(nodes.con);
88+
expect(schema2.obj.val).toBeInstanceOf(nodes.val);
89+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {s} from '../../../json-crdt-patch';
2+
import {Model} from '../../model';
3+
import {JsonNodeToSchema, SchemaToJsonNode} from '../types';
4+
5+
describe('can infer schema of JSON CRDT nodes', () => {
6+
test('con', () => {
7+
const schema1 = s.con(123);
8+
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1;
9+
});
10+
11+
test('val', () => {
12+
const schema1 = s.val(s.con(true));
13+
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1;
14+
});
15+
16+
test('obj', () => {
17+
const schema1 = s.obj({
18+
hello: s.con('world'),
19+
});
20+
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1;
21+
});
22+
23+
test('vec', () => {
24+
const schema1 = s.vec(s.con(1), s.val(s.con(2)));
25+
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1;
26+
});
27+
28+
test('str', () => {
29+
const schema1 = s.str('asdf');
30+
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1;
31+
});
32+
33+
test('bin', () => {
34+
const schema1 = s.bin(new Uint8Array([1, 2, 3]));
35+
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1;
36+
});
37+
38+
test('arr', () => {
39+
const schema1 = s.arr([s.con(1), s.val(s.con(2))]);
40+
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1;
41+
});
42+
43+
test('from typed model', () => {
44+
const model = Model.withLogicalClock().setSchema(
45+
s.obj({
46+
id: s.con('asdf'),
47+
age: s.val(s.con(42)),
48+
}),
49+
);
50+
type Node = ReturnType<typeof model.root.node>;
51+
type Schema = JsonNodeToSchema<Node>;
52+
const schema: Schema = s.obj({
53+
id: s.con('asdf'),
54+
age: s.val(s.con(42)),
55+
});
56+
});
57+
});

src/json-crdt/schema/toSchema.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {JsonNode, ConNode, ValNode, ObjNode, VecNode, StrNode, BinNode, ArrNode} from '../nodes';
2+
import {NodeBuilder, s} from '../../json-crdt-patch';
3+
import type {JsonNodeToSchema} from './types';
4+
5+
/**
6+
* Converts any JSON CRDT node to a schema representation. The schema can be
7+
* used to copy the structure of the JSON CRDT node to another document or
8+
* another location in the same document.
9+
*
10+
* @param node JSON CRDT node to recursively convert to schema.
11+
* @returns Schema representation of the JSON CRDT node.
12+
*/
13+
export const toSchema = <N extends JsonNode<any>>(node: N): JsonNodeToSchema<N> => {
14+
if (node instanceof ConNode) return s.con(node.val) as any;
15+
if (node instanceof ValNode) return s.val(toSchema(node.node())) as any;
16+
if (node instanceof ObjNode) {
17+
const obj: Record<string, NodeBuilder> = {};
18+
node.nodes((child, key) => (obj[key] = toSchema(child)));
19+
return s.obj(obj) as any;
20+
}
21+
if (node instanceof VecNode) {
22+
const arr: NodeBuilder[] = [];
23+
node.children((child) => arr.push(toSchema(child)));
24+
return s.vec(...arr) as any;
25+
}
26+
if (node instanceof StrNode) return s.str(node.view()) as any;
27+
if (node instanceof BinNode) return s.bin(node.view()) as any;
28+
if (node instanceof ArrNode) {
29+
const arr: NodeBuilder[] = [];
30+
node.children((child) => {
31+
if (child) arr.push(toSchema(child));
32+
});
33+
return s.arr(arr) as any;
34+
}
35+
return s.con(undefined) as any;
36+
};

src/json-crdt/schema/types.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type {nodes as builder} from '../../json-crdt-patch';
2+
import type * as nodes from '../nodes';
3+
4+
// prettier-ignore
5+
export type SchemaToJsonNode<S> = S extends builder.str<infer T>
6+
? nodes.StrNode<T>
7+
: S extends builder.bin
8+
? nodes.BinNode
9+
: S extends builder.con<infer T>
10+
? nodes.ConNode<T>
11+
: S extends builder.val<infer T>
12+
? nodes.ValNode<SchemaToJsonNode<T>>
13+
: S extends builder.vec<infer T>
14+
? nodes.VecNode<{[K in keyof T]: SchemaToJsonNode<T[K]>}>
15+
: S extends builder.obj<infer T>
16+
? nodes.ObjNode<{[K in keyof T]: SchemaToJsonNode<T[K]>}>
17+
: S extends builder.arr<infer T>
18+
? nodes.ArrNode<SchemaToJsonNode<T>>
19+
: nodes.JsonNode;
20+
21+
// prettier-ignore
22+
export type JsonNodeToSchema<N> = N extends nodes.StrNode<infer T>
23+
? builder.str<T>
24+
: N extends nodes.BinNode
25+
? builder.bin
26+
: N extends nodes.ConNode<infer T>
27+
? builder.con<T>
28+
: N extends nodes.ValNode<infer T>
29+
? builder.val<JsonNodeToSchema<T>>
30+
: N extends nodes.VecNode<infer T>
31+
? builder.vec<{[K in keyof T]: JsonNodeToSchema<T[K]>}>
32+
: N extends nodes.ObjNode<infer T>
33+
? builder.obj<{[K in keyof T]: JsonNodeToSchema<T[K]>}>
34+
: N extends nodes.ArrNode<infer T>
35+
? builder.arr<JsonNodeToSchema<T>>
36+
: builder.con<undefined>;

0 commit comments

Comments
 (0)