Skip to content

Commit 6b8de6f

Browse files
committed
feat(json-crdt): 🎸 improve Model.create() schema inference from types
1 parent 2826899 commit 6b8de6f

File tree

3 files changed

+93
-73
lines changed

3 files changed

+93
-73
lines changed

src/json-crdt/model/Model.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import * as operations from '../../json-crdt-patch/operations';
22
import * as clock from '../../json-crdt-patch/clock';
3-
import {ConNode} from '../nodes/const/ConNode';
43
import {encoder, decoder} from '../codec/structural/binary/shared';
54
import {ModelApi} from './api';
65
import {ORIGIN, SESSION, SYSTEM_SESSION_TIME} from '../../json-crdt-patch/constants';
76
import {randomSessionId} from './util';
8-
import {RootNode, ValNode, VecNode, ObjNode, StrNode, BinNode, ArrNode} from '../nodes';
7+
import {ConNode, RootNode, ValNode, VecNode, ObjNode, StrNode, BinNode, ArrNode} from '../nodes';
98
import {printTree} from 'tree-dump/lib/printTree';
109
import {Extensions} from '../extensions/Extensions';
1110
import {AvlMap} from 'sonic-forest/lib/avl/AvlMap';
12-
import {NodeBuilder, s} from '../../json-crdt-patch';
11+
import {NodeBuilder, type nodes, s} from '../../json-crdt-patch';
1312
import type {SchemaToJsonNode} from '../schema/types';
1413
import type {JsonCrdtPatchOperation, Patch} from '../../json-crdt-patch/Patch';
1514
import type {JsonNode, JsonNodeView} from '../nodes/types';
@@ -126,16 +125,16 @@ export class Model<N extends JsonNode = JsonNode<any>> implements Printable {
126125
public static readonly create = <S extends NodeBuilder | unknown>(
127126
schema?: S,
128127
sidOrClock: clock.ClockVector | number = Model.sid(),
129-
): Model<S extends NodeBuilder ? SchemaToJsonNode<S> : JsonNode> => {
128+
) => {
130129
const cl =
131130
typeof sidOrClock === 'number'
132131
? sidOrClock === SESSION.SERVER
133132
? new clock.ServerClockVector(SESSION.SERVER, 1)
134133
: new clock.ClockVector(sidOrClock, 1)
135134
: sidOrClock;
136-
type Node = S extends NodeBuilder ? SchemaToJsonNode<S> : JsonNode;
135+
type Node = S extends undefined ? JsonNode : S extends NodeBuilder ? SchemaToJsonNode<S> : SchemaToJsonNode<nodes.json<S>>;
137136
const model = new Model<Node>(cl);
138-
if (schema) model.setSchema(schema instanceof NodeBuilder ? schema : s.json(schema), true);
137+
if (schema !== void 0) model.setSchema(schema instanceof NodeBuilder ? schema : s.json(schema), true);
139138
return model;
140139
};
141140

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {type nodes, s} from '../../../json-crdt-patch';
2+
import {SESSION} from '../../../json-crdt-patch/constants';
3+
import {Model} from '../Model';
4+
5+
describe('.setSchema()', () => {
6+
test('can set object schema', () => {
7+
const model = Model.create().setSchema(
8+
s.obj({
9+
str: s.str('asdf'),
10+
con: s.con(123),
11+
}),
12+
);
13+
expect(model.s.str.toApi().view()).toBe('asdf');
14+
expect(model.s.con.toApi().view()).toBe(123);
15+
});
16+
17+
test('can set map schema', () => {
18+
const model = Model.create().setSchema(
19+
s.map<nodes.str | nodes.con<number>>({
20+
str: s.str('asdf'),
21+
con1: s.con(123),
22+
}),
23+
);
24+
expect(model.s.str.toApi().view()).toBe('asdf');
25+
expect(model.s.con1.toApi().view()).toBe(123);
26+
expect(model.view().str).toBe('asdf');
27+
expect(model.view().con1).toBe(123);
28+
expect(model.view().anyKeyAllowed).toBe(undefined);
29+
});
30+
31+
test('uses global session ID by default', () => {
32+
const model = Model.create().setSchema(
33+
s.obj({
34+
id: s.str<string>('asdf'),
35+
num: s.con(123),
36+
}),
37+
);
38+
expect(model.api.r.get().node.id.sid).toBe(SESSION.GLOBAL);
39+
expect(model.api.r.get().get('id').node.id.sid).toBe(SESSION.GLOBAL);
40+
expect(model.api.r.get().get('num').node.id.sid).toBe(SESSION.GLOBAL);
41+
});
42+
43+
test('allows to specify custom session ID', () => {
44+
const schema = s.obj({
45+
id: s.str<string>('asdf'),
46+
num: s.con(123),
47+
});
48+
const model = Model.create().setSchema(schema, false);
49+
expect(model.api.r.get().node.id.sid).toBe(model.clock.sid);
50+
expect(model.api.r.get().get('id').node.id.sid).toBe(model.clock.sid);
51+
expect(model.api.r.get().get('num').node.id.sid).toBe(model.clock.sid);
52+
});
53+
54+
test('resets session ID to user specified', () => {
55+
const model = Model.create().setSchema(
56+
s.obj({
57+
id: s.str<string>('asdf'),
58+
num: s.con(123),
59+
}),
60+
);
61+
expect(model.view().num).toBe(123);
62+
expect(model.api.r.get().get('num').node.id.sid).toBe(SESSION.GLOBAL);
63+
model.api.r.get().set({
64+
num: 456,
65+
});
66+
expect(model.view().num).toBe(456);
67+
expect(model.api.r.get().get('num').node.id.sid).not.toBe(SESSION.GLOBAL);
68+
});
69+
});
70+
71+
describe('.create<Schema>()', () => {
72+
test('infers schema from a pojo', () => {
73+
const model = Model.create({
74+
key1: 'value1',
75+
key2: {
76+
key3: [{
77+
foo: 'bar',
78+
}],
79+
},
80+
});
81+
expect(model.$.key2.key3[0].foo.$?.view()).toBe('bar');
82+
});
83+
84+
test('when schema not set, does not strictly type accessor', () => {
85+
const model = Model.create(void 0);
86+
expect(model.$.key2.key3[0].foo.$?.view()).toBe(undefined);
87+
});
88+
});

src/json-crdt/model/__tests__/Model.setSchema.spec.ts

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)