Skip to content

Commit

Permalink
feat(json-crdt): 🎸 use Extension class to construct extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed May 3, 2024
1 parent 675319b commit 273d013
Show file tree
Hide file tree
Showing 17 changed files with 90 additions and 92 deletions.
10 changes: 4 additions & 6 deletions src/json-crdt-extensions/ModelWithExt.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import * as clock from '../json-crdt-patch/clock';
import * as ext from './ext';
import {NodeBuilder} from '../json-crdt-patch';
import {Extensions} from '../json-crdt/extensions/Extensions';
import {Model} from '../json-crdt/model';
import {SchemaToJsonNode} from '../json-crdt/schema/types';
import {CntExt} from './cnt';
import {MvalExt} from './mval';
import {PeritextExt} from './peritext';

const extensions = new Extensions();

extensions.register(CntExt);
extensions.register(MvalExt);
extensions.register(PeritextExt);
extensions.register(ext.cnt);
extensions.register(ext.mval);
extensions.register(ext.peritext);

export class ModelWithExt {
public static readonly create = <S extends NodeBuilder>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {CntExt} from '..';
import {cnt} from '..';
import {Model} from '../../../json-crdt/model';

test('can set new values in single fork', () => {
const model = Model.withLogicalClock();
model.ext.register(CntExt);
model.ext.register(cnt);
model.api.root({
counter: CntExt.new(24),
counter: cnt.new(24),
});
expect(model.view()).toEqual({counter: 24});
const counter = model.api.in(['counter']).asExt(CntExt);
const counter = model.api.in(['counter']).asExt(cnt);
expect(counter.view()).toBe(24);
counter.inc(2);
expect(model.view()).toEqual({counter: 26});
Expand All @@ -18,15 +18,15 @@ test('can set new values in single fork', () => {

test('two concurrent users can increment the counter', () => {
const model = Model.withLogicalClock();
model.ext.register(CntExt);
model.ext.register(cnt);
model.api.root({
counter: CntExt.new(),
counter: cnt.new(),
});
expect(model.view()).toEqual({counter: 0});
const counter = model.api.in(['counter']).asExt(CntExt);
const counter = model.api.in(['counter']).asExt(cnt);
expect(counter.view()).toBe(0);
const model2 = model.fork();
const counter2 = model2.api.in(['counter']).asExt(CntExt);
const counter2 = model2.api.in(['counter']).asExt(cnt);
counter.inc(2);
counter2.inc(3);
model.applyPatch(model2.api.flush());
Expand Down
26 changes: 14 additions & 12 deletions src/json-crdt-extensions/cnt/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {ExtensionId, ExtensionName} from '../constants';
import {NodeApi} from '../../json-crdt/model/api/nodes';
import {ExtNode} from '../../json-crdt/extensions/ExtNode';
import {s, type ExtensionDefinition, type ObjNode} from '../../json-crdt';
import {ExtensionNode} from '../../json-crdt/extensions/ExtensionNode';
import {Extension} from '../../json-crdt/extensions/Extension';
import {nodes, s, type ObjNode} from '../../json-crdt';
import type {ExtensionApi} from '../../json-crdt';

const MNEMONIC = ExtensionName[ExtensionId.cnt];

class CntNode extends ExtNode<ObjNode> {
// ------------------------------------------------------------------ ExtNode

class CntNode extends ExtensionNode<ObjNode> {
public name(): string {
return MNEMONIC;
}
Expand Down Expand Up @@ -36,10 +35,13 @@ class CntApi extends NodeApi<CntNode> implements ExtensionApi<CntNode> {
}
}

export const CntExt: ExtensionDefinition<ObjNode, CntNode, CntApi> = {
id: ExtensionId.cnt,
name: MNEMONIC,
new: (value?: number, sid: number = 0) => s.ext(ExtensionId.cnt, s.obj({[sid]: s.jsonCon(value)})),
Node: CntNode,
Api: CntApi,
};
export const cnt = new Extension<
ExtensionId.cnt,
ObjNode,
CntNode,
CntApi,
[value?: number, sid?: number],
nodes.map<nodes.con<number>>
>(ExtensionId.cnt, MNEMONIC, CntNode, CntApi, (value?: any, sid: any = 0) =>
value === undefined ? s.map<nodes.con<number>>({}) : s.map<nodes.con<number>>({[sid]: s.con(value ?? 0)}),
);
9 changes: 9 additions & 0 deletions src/json-crdt-extensions/ext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {cnt} from './cnt';
import {mval} from './mval';
import {peritext} from './peritext';

export {
cnt,
mval,
peritext,
};
3 changes: 3 additions & 0 deletions src/json-crdt-extensions/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export * from './mval';
export * from './cnt';
export * from './peritext';
export * from './ext';
export * from './ModelWithExt';
export * from './constants';
6 changes: 2 additions & 4 deletions src/json-crdt-extensions/mval/MvalNode.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {MNEMONIC} from './constants';
import {ExtNode} from '../../json-crdt/extensions/ExtNode';
import {ExtensionNode} from '../../json-crdt/extensions/ExtensionNode';
import type {ArrNode} from '../../json-crdt/nodes/arr/ArrNode';

export class MvalNode extends ExtNode<ArrNode> {
// ------------------------------------------------------------------ ExtNode

export class MvalNode extends ExtensionNode<ArrNode> {
public name(): string {
return MNEMONIC;
}
Expand Down
17 changes: 9 additions & 8 deletions src/json-crdt-extensions/mval/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import {ExtensionId} from '../constants';
import {MvalNode} from './MvalNode';
import {MvalApi} from './MvalApi';
import {MNEMONIC} from './constants';
import {s} from '../../json-crdt';
import {Extension} from '../../json-crdt/extensions/Extension';
import type {ITimestampStruct} from '../../json-crdt-patch/clock';
import type {ArrNode} from '../../json-crdt/nodes/arr/ArrNode';
import {s, type ExtensionDefinition} from '../../json-crdt';

export const MvalExt: ExtensionDefinition<ArrNode, MvalNode, MvalApi> = {
id: ExtensionId.mval,
name: MNEMONIC,
new: (value: unknown | ITimestampStruct) => s.ext(ExtensionId.mval, s.arr<any>([s.json(value)])),
Node: MvalNode,
Api: MvalApi,
};
export const mval = new Extension<ExtensionId.mval, ArrNode, MvalNode, MvalApi, [value: unknown | ITimestampStruct]>(
ExtensionId.mval,
MNEMONIC,
MvalNode,
MvalApi,
(value: unknown | ITimestampStruct) => s.arr<any>([s.json(value)]),
);
6 changes: 2 additions & 4 deletions src/json-crdt-extensions/peritext/PeritextNode.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {MNEMONIC} from './constants';
import {ExtNode} from '../../json-crdt/extensions/ExtNode';
import {ExtensionNode} from '../../json-crdt/extensions/ExtensionNode';
import type {PeritextDataNode} from './types';

export class PeritextNode extends ExtNode<PeritextDataNode> {
// ------------------------------------------------------------------ ExtNode

export class PeritextNode extends ExtensionNode<PeritextDataNode> {
public name(): string {
return MNEMONIC;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ test('..', () => {
text: PeritextExt.new('Hello, world\n'),
});

console.log('' + model);
// console.log('' + model);
});
4 changes: 1 addition & 3 deletions src/json-crdt-extensions/peritext/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const enum Chars {

export const MNEMONIC = ExtensionName[ExtensionId.peritext];

export const BUILD_SCHEMA = (text: string) =>
export const SCHEMA = (text: string) =>
s.vec<
[
/**
Expand All @@ -23,5 +23,3 @@ export const BUILD_SCHEMA = (text: string) =>
slices: nodes.arr<SliceSchema>,
]
>(s.str<string>(text), s.arr<SliceSchema>([]));

export const SCHEMA = BUILD_SCHEMA('');
18 changes: 9 additions & 9 deletions src/json-crdt-extensions/peritext/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {ExtensionId} from '../constants';
import {PeritextNode} from './PeritextNode';
import {PeritextApi} from './PeritextApi';
import {BUILD_SCHEMA, MNEMONIC} from './constants';
import {SCHEMA, MNEMONIC} from './constants';
import {Extension} from '../../json-crdt/extensions/Extension';
import type {PeritextDataNode} from './types';
import {s, type ExtensionDefinition} from '../../json-crdt';

export const PeritextExt: ExtensionDefinition<PeritextDataNode, PeritextNode, PeritextApi> = {
id: ExtensionId.peritext,
name: MNEMONIC,
new: (text: string) => s.ext(ExtensionId.peritext, BUILD_SCHEMA(text)),
Node: PeritextNode,
Api: PeritextApi,
};
export const peritext = new Extension<
ExtensionId.peritext,
PeritextDataNode,
PeritextNode,
PeritextApi,
[text: string]
>(ExtensionId.peritext, MNEMONIC, PeritextNode, PeritextApi, (text: string) => SCHEMA(text));
2 changes: 1 addition & 1 deletion src/json-crdt-extensions/peritext/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ export interface Stateful {
refresh(): number;
}

export type PeritextDataNodeSchema = typeof SCHEMA;
export type PeritextDataNodeSchema = ReturnType<typeof SCHEMA>;
export type PeritextDataNode = SchemaToJsonNode<PeritextDataNodeSchema>;
24 changes: 14 additions & 10 deletions src/json-crdt/extensions/Extension.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import {s, type NodeBuilder, type nodes} from '../../json-crdt-patch';
import {NodeBuilder, s, type nodes} from '../../json-crdt-patch';
import type {ModelApi} from '../model';
import type {JsonNode} from '../nodes';
import type {JsonNodeToSchema} from '../schema/types';
import type {ExtensionApi, ExtensionJsonNode} from './types';

export type AnyExtension = Extension<any, any, any, any, any, any>;

export class Extension<
Id extends number,
Node extends JsonNode,
ENode extends ExtensionJsonNode,
EApi extends ExtensionApi<ENode>,
ESchema extends NodeBuilder,
DataNode extends JsonNode,
ExtNode extends ExtensionJsonNode<DataNode>,
ExtApi extends ExtensionApi<ExtNode>,
DataArgs extends any[] = any[],
DataSchema extends NodeBuilder = JsonNodeToSchema<DataNode>,
> {
constructor(
public readonly id: Id,
public readonly name: string,
public readonly schema: (...args: any[]) => ESchema,
public readonly Node: new (data: Node) => ENode,
public readonly Api: new (node: ENode, api: ModelApi) => EApi,
public readonly Node: new (data: DataNode) => ExtNode,
public readonly Api: new (node: ExtNode, api: ModelApi) => ExtApi,
public readonly schema: (...args: DataArgs) => DataSchema,
) {}

public new(...args: any[]): nodes.ext<Id, ESchema> {
return s.ext<Id, ESchema>(this.id, this.schema(...args));
public new(...args: DataArgs): nodes.ext<Id, DataSchema> {
return s.ext<Id, DataSchema>(this.id, this.schema(...args));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {ITimestampStruct} from '../../json-crdt-patch/clock';
import type {ExtensionJsonNode, JsonNode} from '..';
import type {Printable} from 'tree-dump/lib/types';

export abstract class ExtNode<N extends JsonNode> implements ExtensionJsonNode, Printable {
export abstract class ExtensionNode<N extends JsonNode> implements ExtensionJsonNode<N>, Printable {
public readonly id: ITimestampStruct;

constructor(public readonly data: N) {
Expand Down
8 changes: 4 additions & 4 deletions src/json-crdt/extensions/Extensions.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {printTree} from 'tree-dump/lib/printTree';
import type {ExtensionDefinition} from './types';
import type {AnyExtension} from './Extension';
import type {Printable} from 'tree-dump/lib/types';

export class Extensions implements Printable {
protected readonly ext: Record<number, ExtensionDefinition> = {};
protected readonly ext: Record<number, AnyExtension> = {};

public register(extension: ExtensionDefinition<any, any, any>) {
public register(extension: AnyExtension) {
this.ext[extension.id] = extension;
}

public get(id: number): ExtensionDefinition | undefined {
public get(id: number): AnyExtension | undefined {
return this.ext[id];
}

Expand Down
20 changes: 3 additions & 17 deletions src/json-crdt/extensions/types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
import type {NodeBuilder} from '../../json-crdt-patch/builder/DelayedValueBuilder';
import type {ModelApi} from '../model/api/ModelApi';
import type {NodeApi} from '../model/api/nodes';
import type {JsonNode} from '../nodes';
import type {ExtensionNode} from './ExtensionNode';

export type ExtensionValue = [type: Uint8Array, data: unknown];

export interface ExtensionJsonNode extends JsonNode {}
export interface ExtensionJsonNode<N extends JsonNode> extends ExtensionNode<N> {}

export interface ExtensionApi<EN extends ExtensionJsonNode> extends NodeApi<EN> {}

export interface ExtensionDefinition<
Node extends JsonNode = JsonNode,
ENode extends ExtensionJsonNode = ExtensionJsonNode,
EApi extends ExtensionApi<ENode> = ExtensionApi<ENode>,
ESchema extends NodeBuilder = NodeBuilder,
> {
id: number;
name: string;
new: (...args: any[]) => ESchema;
Node: new (data: Node) => ENode;
Api: new (node: ENode, api: ModelApi) => EApi;
}
export interface ExtensionApi<EN extends ExtensionJsonNode<any>> extends NodeApi<EN> {}
9 changes: 5 additions & 4 deletions src/json-crdt/model/api/nodes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {printTree} from 'tree-dump/lib/printTree';
import {find} from './find';
import {ITimestampStruct, Timestamp} from '../../../json-crdt-patch/clock';
import {Path} from '../../../json-pointer';
import {ObjNode, ArrNode, BinNode, ConNode, VecNode, ValNode, StrNode} from '../../nodes';
import {ExtensionApi, ExtensionDefinition, ExtensionJsonNode} from '../../extensions/types';
import {ExtensionApi, ExtensionJsonNode} from '../../extensions/types';
import {NodeEvents} from './NodeEvents';
import {printTree} from 'tree-dump/lib/printTree';
import type {Extension} from '../../extensions/Extension';
import type {JsonNode, JsonNodeView} from '../../nodes';
import type * as types from './proxy';
import type {ModelApi} from './ModelApi';
Expand Down Expand Up @@ -110,8 +111,8 @@ export class NodeApi<N extends JsonNode = JsonNode> implements Printable {
throw new Error('NOT_CONST');
}

public asExt<EN extends ExtensionJsonNode, V, EApi extends ExtensionApi<EN>>(
ext: ExtensionDefinition<any, EN, EApi>,
public asExt<EN extends ExtensionJsonNode<any>, EApi extends ExtensionApi<EN>>(
ext: Extension<any, any, EN, EApi, any, any>,
): EApi {
let node: JsonNode | undefined = this.node;
while (node) {
Expand Down

0 comments on commit 273d013

Please sign in to comment.