Skip to content

Instantly share code, notes, and snippets.

@mbalex99
Last active July 8, 2019 23:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbalex99/f2e97be6cf65161a723d2d5aff690aa6 to your computer and use it in GitHub Desktop.
Save mbalex99/f2e97be6cf65161a723d2d5aff690aa6 to your computer and use it in GitHub Desktop.
GCounter and PNCounter
function checkSiteId(siteId: any): siteId is string {
if (typeof siteId !== "string") {
return false
}
if (siteId.trim().length === 0) {
// it's just white space
return false
}
return true
}
type GCounterState = { [siteId: string]: number }
/**
* This is a Grow Only Counter. You can only increment it!
*/
export class GCounter {
/**
* This is the internal state of the counter.
* Never mutate this directly. Send this over the wire to be merged with another replica
*/
public state: GCounterState = {};
private _siteId: string | undefined | null;
public get siteId(): string | undefined | null {
return this._siteId;
}
constructor(siteId: string | null | undefined = undefined, initialValue: number = 0) {
this._siteId = siteId;
if (checkSiteId(siteId)) {
this.state[siteId] = initialValue
}
}
/**
* Reads the value of the counter.
*/
get value(): number {
return Object.values(this.state).reduce((a, b) => a + b, 0);
}
/**
* Increment the counter with a value above or equal to 0. This cannot be negative,
* or else this method with throw an error.
* @param @type {number} value A non negative number value
*/
increment(value: number) {
if (!checkSiteId(this._siteId)) {
throw "Cannot increment without a valid siteId, please set a siteId";
}
if (value < 0) {
throw "Cannot increment lower than 0";
}
if (this.state[this._siteId]) {
this.state[this._siteId] = this.state[this._siteId] + value;
} else {
this.state[this._siteId] = value;
}
}
merge(other: GCounterState | GCounter) {
let anotherState: GCounterState = {}
if (other instanceof GCounter) {
anotherState = other.state
} else {
anotherState = other;
}
const setOfSiteIds = new Set<string>();
for (let siteId of Object.keys(this.state)) {
setOfSiteIds.add(siteId);
}
for (let siteId of Object.keys(anotherState)) {
setOfSiteIds.add(siteId);
}
let finalMap: GCounterState = {};
for (let siteId of setOfSiteIds) {
finalMap[siteId] = Math.max(
this.state[siteId] || 0,
anotherState[siteId] || 0
);
}
this.state = finalMap;
}
public static fromState(state: { [siteId: string]: number }): GCounter {
const counter = new GCounter();
counter.state = state;
return counter;
}
}
interface PNCounterState {
incrementCounters: GCounterState
decrementCounters: GCounterState
}
/**
* Unlike the GCounter, the PNCounter can be positively and negatively incremented.
*/
export class PNCounter {
private incrementCounters: GCounter
private decrementCounters: GCounter
private _siteId: string | null | undefined;
public get siteId(): string | null | undefined {
return this._siteId
}
constructor(siteId: string | null | undefined = undefined) {
this.incrementCounters = new GCounter(siteId)
this.decrementCounters = new GCounter(siteId)
}
get value(): number {
return this.incrementCounters.value - this.decrementCounters.value
}
get state(): PNCounterState {
return {
incrementCounters: this.incrementCounters.state,
decrementCounters: this.decrementCounters.state
}
}
merge(other: PNCounterState | PNCounter) {
let anotherState: PNCounterState = {
incrementCounters: {},
decrementCounters: {}
}
if (other instanceof PNCounter) {
anotherState.incrementCounters = other.incrementCounters.state
anotherState.decrementCounters = other.decrementCounters.state
} else {
anotherState.incrementCounters = other.incrementCounters
anotherState.decrementCounters = other.decrementCounters
}
this.incrementCounters.merge(anotherState.incrementCounters);
this.decrementCounters.merge(anotherState.decrementCounters);
}
/**
* Increments a negative, 0 or positive value.
* @param value A number value (both negative, 0 or positive)
*/
increment(value: number) {
if (value > 0) {
this.incrementCounters.increment(value)
} else {
this.decrementCounters.increment( -1 * value )
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment