@terrencecrowley/ot-js
Version:
Javascript OT library
143 lines (124 loc) • 3.44 kB
text/typescript
import * as OT from "./ottypes";
// This implements OT for a dictionary of objects. OT is pretty trivial for maps - last wins.
export const OpMapSet = 1;
export const OpMapDel = 2;
export type MapEdit = [ number, string, any ]; // Op, Key, Value
export class OTMapResource extends OT.OTResourceBase
{
constructor(rid: string)
{
super(rid, 'map');
}
static factory(rid: string): OTMapResource { return new OTMapResource(rid); }
// Set a property
set(p: string, a: any): OTMapResource
{
this.edits.push([ OpMapSet, p, a ]);
return this;
}
// Delete a property
del(p: string): OTMapResource
{
this.edits.push([ OpMapDel, p, 0 ]);
return this;
}
// copy an instance
copy(): OTMapResource
{
let c: OTMapResource = new OTMapResource(this.resourceName);
for (let i: number = 0; i < this.length; i++)
{
let e: MapEdit = this.edits[i];
c.edits.push([ e[0], e[1], e[2] ]);
}
return c;
}
// Test whether two operations are effectively equivalent
effectivelyEqual(rhs: OTMapResource): boolean
{
// This should really be a structural error
if (this.length != rhs.length)
return false;
// This checks for exact structural equivalency. Really the ordering shouldn't matter for Map so
// an improvement to this algorithm would be to be more robust to ordering differences.
for (let i: number = 0; i < this.length; i++)
{
let e1: MapEdit = this.edits[i];
let e2: MapEdit = rhs.edits[i];
if (e1[0] != e2[0] || e1[1] != e2[1] || e1[2] != e2[2])
return false;
}
return true;
}
// Core OT algorithm for this type
transform(prior: OTMapResource, bPriorIsService: boolean): void
{
// Last wins - if I'm last, my sets and deletes are all preserved
if (bPriorIsService)
return;
// OK, remove any operations (either sets or deletes), that conflict with me
// First load in my properties
let myEdits: any = this.toObject();
// Now delete any that are overridden
for (let i: number = 0; i < prior.length; i++)
delete myEdits[(prior.edits[i])[1]];
// Now restore edit array from edited object
this.fromObject(myEdits);
}
// compose two edit actions
compose(rhs: OTMapResource): void // throws on error
{
let o: any = this.toObject();
for (let i: number = 0; i < rhs.length; i++)
{
let eR: MapEdit = rhs.edits[i];
o[eR[1]] = [ eR[0], eR[1], eR[2] ]; // Note this overwrites any existing operation on this key, set or del
}
this.fromObject(o);
}
apply(startValue: any): any
{
if (startValue == null)
startValue = { };
for (let i: number = 0; i < this.length; i++)
{
let e: MapEdit = this.edits[i];
switch (e[0])
{
case OpMapSet:
startValue[e[1]] = e[2];
break;
case OpMapDel:
delete startValue[e[1]];
break;
}
}
return startValue;
}
minimize(): any
{
// Effectively removes OpMapDel
let o: any = this.apply({});
this.edits = [];
for (var p in o)
if (o.hasOwnProperty(p))
this.edits.push([ OpMapSet, p, o[p] ]);
}
loadObject(o: any): any
{
for (let i: number = 0; i < this.length; i++)
o[(this.edits[i])[1]] = this.edits[i];
return o;
}
toObject(): any
{
return this.loadObject({ });
}
fromObject(o: any): void
{
this.edits = [];
for (var p in o)
if (o.hasOwnProperty(p))
this.edits.push(o[p]);
}
}