UNPKG

mute-structs

Version:

NodeJS module providing an implementation of the LogootSplit CRDT algorithm

184 lines (180 loc) 8.46 kB
/* 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