UNPKG

mute-structs

Version:

NodeJS module providing an implementation of the LogootSplit CRDT algorithm

132 lines (108 loc) 4.22 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 { isArrayFromMap, isObject } from "../data-validation" import { Ordering } from "../ordering" import { Epoch } from "./epoch" import { EpochId } from "./epochid" export interface EpochStoreJSON { readonly epochs: Array<[string, Epoch]> } export function compareEpochFullIds (id1: number[], id2: number[]): Ordering { const minLength = id1.length < id2.length ? id1.length : id2.length for (let i = 0; i < minLength; i++) { const value1 = id1[i] const value2 = id2[i] if (value1 < value2) { return Ordering.Less } else if (value1 > value2) { return Ordering.Greater } } if (id1.length < id2.length) { return Ordering.Less } else if (id1.length > id2.length) { return Ordering.Greater } else { return Ordering.Equal } } export class EpochStore { static fromPlain (o: unknown): EpochStore | null { if (isObject<EpochStoreJSON>(o) && Array.isArray(o.epochs) && o.epochs.length > 0) { const epochs = o.epochs .filter(isArrayFromMap) .map(([_, v]) => { return Epoch.fromPlain(v) }) .filter((epoch): epoch is Epoch => epoch !== null) if (o.epochs.length === epochs.length) { const [origin, ...rest] = epochs const epochStore = new EpochStore(origin) rest.forEach((epoch) => { epochStore.addEpoch(epoch) }) return epochStore } } return null } private epochs: Map<string, Epoch> constructor (origin: Epoch) { this.epochs = new Map() this.addEpoch(origin) } addEpoch (epoch: Epoch) { this.epochs.set(epoch.id.asStr, epoch) } hasEpoch (epoch: Epoch): boolean { return this.epochs.has(epoch.id.asStr) } getEpoch (epochId: EpochId): Epoch | undefined { return this.epochs.get(epochId.asStr) } getEpochFullId (epoch: Epoch): number[] { console.assert(this.hasEpoch(epoch), "The epoch should have been added to the store previously") let parentEpochFullId: number[] = [] if (epoch.parentId !== undefined) { const parentEpoch = this.getEpoch(epoch.parentId) if (parentEpoch !== undefined) { parentEpochFullId = this.getEpochFullId(parentEpoch) } } return parentEpochFullId.concat(epoch.id.replicaNumber, epoch.id.epochNumber) } getEpochPath (epoch: Epoch): Epoch[] { const pathEpoch: Epoch[] = [] let currentEpoch: Epoch | undefined = epoch while (currentEpoch !== undefined) { pathEpoch.push(currentEpoch) currentEpoch = currentEpoch.parentId !== undefined ? this.getEpoch(currentEpoch.parentId) : undefined } return pathEpoch.reverse() } getPathBetweenEpochs (from: Epoch, to: Epoch): [Epoch[], Epoch[]] { const fromPath = this.getEpochPath(from) const toPath = this.getEpochPath(to) let i = 0 while (i < fromPath.length && i < toPath.length && fromPath[i].equals(toPath[i])) { i++ } return [fromPath.slice(i).reverse(), toPath.slice(i)] } toJSON (): EpochStoreJSON { return { epochs: Array.from(this.epochs) } } }