UNPKG

@terrencecrowley/ot-js

Version:
143 lines (124 loc) 3.44 kB
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]); } }