mute-structs
Version:
NodeJS module providing an implementation of the LogootSplit CRDT algorithm
184 lines (180 loc) • 8.46 kB
JavaScript
/*
This file is part of MUTE-structs.
Copyright (C) 2017 Matthieu Nicolas, Victorien Elvinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { isObject } from "./data-validation";
import { Epoch } from "./epoch/epoch";
import { EpochId } from "./epoch/epochid";
import { compareEpochFullIds, EpochStore } from "./epoch/epochstore";
import { IdentifierInterval } from "./identifierinterval";
import { createAtPosition } from "./idfactory";
import { LogootSRopes } from "./logootsropes";
import { LogootSDel } from "./operations/delete/logootsdel";
import { RenamableLogootSDel } from "./operations/delete/renamablelogootsdel";
import { LogootSAdd } from "./operations/insert/logootsadd";
import { RenamableLogootSAdd } from "./operations/insert/renamablelogootsadd";
import { LogootSRename } from "./operations/rename/logootsrename";
import { RenamingMap } from "./renamingmap/renamingmap";
import { RenamingMapStore } from "./renamingmap/renamingmapstore";
import { mkNodeAt } from "./ropesnodes";
function generateInsertOps(idIntervals, str) {
let currentOffset = 0;
return idIntervals
.map((idInterval) => {
const nextOffset = currentOffset + idInterval.length;
const content = str.slice(currentOffset, nextOffset);
currentOffset = nextOffset;
return new LogootSAdd(idInterval.idBegin, content);
});
}
export class RenamableReplicableList {
constructor(list, currentEpoch, epochsStore, renamingMapStore) {
this.list = list;
this.currentEpoch = currentEpoch;
this.epochsStore = epochsStore;
this.renamingMapStore = renamingMapStore;
}
static create(replicaNumber = 0, clock = 0) {
const list = new LogootSRopes(replicaNumber, clock);
const currentEpoch = new Epoch(new EpochId(0, 0));
const epochsStore = new EpochStore(currentEpoch);
const renamingMapStore = new RenamingMapStore();
return new RenamableReplicableList(list, currentEpoch, epochsStore, renamingMapStore);
}
static fromPlain(o) {
if (isObject(o)) {
const list = LogootSRopes.fromPlain(o.list);
const epochsStore = EpochStore.fromPlain(o.epochsStore);
const renamingMapStore = RenamingMapStore.fromPlain(o.renamingMapStore);
const currentEpoch = Epoch.fromPlain(o.currentEpoch);
if (list !== null && epochsStore !== null &&
renamingMapStore !== null && currentEpoch !== null) {
return new RenamableReplicableList(list, currentEpoch, epochsStore, renamingMapStore);
}
}
return null;
}
static fromPlainLogootSRopes(o) {
const list = LogootSRopes.fromPlain(o);
if (list !== null) {
const currentEpoch = new Epoch(new EpochId(0, 0));
const epochsStore = new EpochStore(currentEpoch);
const renamingMapStore = new RenamingMapStore();
return new RenamableReplicableList(list, currentEpoch, epochsStore, renamingMapStore);
}
return null;
}
get replicaNumber() {
return this.list.replicaNumber;
}
get clock() {
return this.list.clock;
}
get currentRenamingMap() {
return this.renamingMapStore.getRenamingMap(this.currentEpoch.id);
}
getList() {
return this.list;
}
getCurrentEpoch() {
return this.currentEpoch;
}
get str() {
return this.list.str;
}
insertLocal(pos, l) {
return new RenamableLogootSAdd(this.list.insertLocal(pos, l), this.currentEpoch);
}
insertRemote(epoch, op) {
if (!epoch.equals(this.currentEpoch)) {
const strat = (rmap, ids) => rmap.initRenameIds(ids);
const newIds = this.renameFromEpochToCurrent(op.insertedIds, epoch, strat);
const newIdIntervals = IdentifierInterval.mergeIdsIntoIntervals(newIds);
const insertOps = generateInsertOps(newIdIntervals, op.content);
return insertOps
.flatMap((insertOp) => insertOp.execute(this.list));
}
return op.execute(this.list);
}
delLocal(begin, end) {
return new RenamableLogootSDel(this.list.delLocal(begin, end), this.currentEpoch);
}
delRemote(epoch, op) {
if (!epoch.equals(this.currentEpoch)) {
const idsToRename = op.lid
.flatMap((idInterval) => idInterval.toIds());
const strat = (rmap, ids) => rmap.initRenameIds(ids);
const newIds = this.renameFromEpochToCurrent(idsToRename, epoch, strat);
const newIdIntervals = IdentifierInterval.mergeIdsIntoIntervals(newIds);
const newOp = new LogootSDel(newIdIntervals, op.author);
return newOp.execute(this.list);
}
return op.execute(this.list);
}
renameLocal() {
const renamedIdIntervals = this.list.toList();
const clock = this.clock;
const newEpochNumber = this.currentEpoch.id.epochNumber + 1;
const newEpochId = new EpochId(this.replicaNumber, newEpochNumber);
this.currentEpoch = new Epoch(newEpochId, this.currentEpoch.id);
this.epochsStore.addEpoch(this.currentEpoch);
const newRandom = renamedIdIntervals[0].idBegin.tuples[0].random;
const renamingMap = new RenamingMap(this.replicaNumber, clock, renamedIdIntervals);
this.renamingMapStore.add(this.currentEpoch, renamingMap);
const baseId = createAtPosition(this.replicaNumber, clock, newRandom, 0);
const newRoot = mkNodeAt(baseId, this.str.length);
this.list = new LogootSRopes(this.replicaNumber, clock + 1, newRoot, this.str);
return new LogootSRename(this.replicaNumber, clock, this.currentEpoch, renamedIdIntervals);
}
renameRemote(replicaNumber, clock, newEpoch, renamedIdIntervals) {
const renamingMap = new RenamingMap(replicaNumber, clock, renamedIdIntervals);
this.epochsStore.addEpoch(newEpoch);
this.renamingMapStore.add(newEpoch, renamingMap);
const newEpochFullId = this.epochsStore.getEpochFullId(newEpoch);
const currentEpochFullId = this.epochsStore.getEpochFullId(this.currentEpoch);
if (compareEpochFullIds(currentEpochFullId, newEpochFullId) === -1 /* Less */) {
const previousEpoch = this.currentEpoch;
this.currentEpoch = newEpoch;
const idsToRename = this.list.toList().flatMap((idInterval) => idInterval.toIds());
const strat = (rmap, ids) => rmap.initRenameSeq(ids);
const newIds = this.renameFromEpochToCurrent(idsToRename, previousEpoch, strat);
const newIdIntervals = IdentifierInterval.mergeIdsIntoIntervals(newIds);
const newList = new LogootSRopes(this.replicaNumber, this.clock);
const insertOps = generateInsertOps(newIdIntervals, this.str);
insertOps.forEach((insertOp) => {
insertOp.execute(newList);
});
this.list = newList;
}
}
renameFromEpochToCurrent(idsToRename, fromEpoch, strat) {
const [epochsToRevert, epochsToApply] = this.epochsStore.getPathBetweenEpochs(fromEpoch, this.currentEpoch);
let ids = idsToRename;
epochsToRevert.forEach((epoch) => {
const rmap = this.renamingMapStore.getRenamingMap(epoch.id);
ids = ids.map((id) => rmap.reverseRenameId(id));
});
epochsToApply.forEach((epoch) => {
const rmap = this.renamingMapStore.getRenamingMap(epoch.id);
ids = strat(rmap, ids);
});
return ids;
}
getNbBlocks() {
return this.list.toList().length;
}
digest() {
return this.list.digest();
}
}
//# sourceMappingURL=renamablereplicablelist.js.map