@lightbend/akkaserverless-javascript-sdk
Version:
Akka Serverless JavaScript SDK
230 lines (213 loc) • 7.63 kB
JavaScript
/*
* Copyright 2021 Lightbend Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const debug = require('debug')('akkaserverless-replicated-entity');
const ReplicatedCounter = require('./counter');
const AnySupport = require('../protobuf-any');
const iterators = require('./iterators');
const util = require('util');
/**
* @classdesc A replicated map of counters.
*
* @constructor module:akkaserverless.replicatedentity.ReplicatedCounterMap
* @implements module:akkaserverless.replicatedentity.ReplicatedData
*/
function ReplicatedCounterMap() {
const counters = new Map();
const removed = new Map();
let cleared = false;
/**
* Get the value at the given key.
*
* @function module:akkaserverless.replicatedentity.ReplicatedCounterMap#get
* @param {module:akkaserverless.Serializable} key The key to get.
* @returns {number|undefined} The counter value, or undefined if no value is defined at that key.
*/
this.get = (key) => {
const entry = counters.get(AnySupport.toComparable(key));
return entry !== undefined ? entry.counter.value : undefined;
};
/**
* Get the value as a long at the given key.
*
* @function module:akkaserverless.replicatedentity.ReplicatedCounterMap#getLong
* @param {module:akkaserverless.Serializable} key The key to get.
* @returns {Long|undefined} The counter value as a long, or undefined if no value is defined at that key.
*/
this.getLong = (key) => {
const entry = counters.get(AnySupport.toComparable(key));
return entry !== undefined ? entry.counter.longValue : undefined;
};
/**
* Increment the counter at the given key by the given number.
*
* @function module:akkaserverless.replicatedentity.ReplicatedCounterMap#increment
* @param {module:akkaserverless.Serializable} key The key for the counter to increment.
* @param {Long|number} increment The amount to increment the counter by. If negative, it will be decremented instead.
* @returns {module:akkaserverless.replicatedentity.ReplicatedCounterMap} This counter map.
*/
this.increment = function (key, increment) {
this.getOrCreateCounter(key).increment(increment);
return this;
};
/**
* Decrement the counter at the given key by the given number.
*
* @function module:akkaserverless.replicatedentity.ReplicatedCounterMap#decrement
* @param {module:akkaserverless.Serializable} key The key for the counter to decrement.
* @param {Long|number} decrement The amount to decrement the counter by. If negative, it will be incremented instead.
* @returns {module:akkaserverless.replicatedentity.ReplicatedCounterMap} This counter map.
*/
this.decrement = function (key, decrement) {
this.getOrCreateCounter(key).decrement(decrement);
return this;
};
/**
* Check whether this map contains a value of the given key.
*
* @function module:akkaserverless.replicatedentity.ReplicatedCounterMap#has
* @param {module:akkaserverless.Serializable} key The key to check.
* @returns {boolean} True if this counter map contains a value for the given key.
*/
this.has = function (key) {
return counters.has(AnySupport.toComparable(key));
};
/**
* The number of elements in this map.
*
* @name module:akkaserverless.replicatedentity.ReplicatedCounterMap#size
* @type {number}
* @readonly
*/
Object.defineProperty(this, 'size', {
get: function () {
return counters.size;
},
});
/**
* Return an iterator of the keys of this counter map.
*
* @function module:akkaserverless.replicatedentity.ReplicatedCounterMap#keys
* @returns {IterableIterator<module:akkaserverless.Serializable>}
*/
this.keys = function () {
return iterators.map(counters.values(), (entry) => entry.key);
};
/**
* Delete the counter at the given key.
*
* @function module:akkaserverless.replicatedentity.ReplicatedCounterMap#delete
* @param {module:akkaserverless.Serializable} key The key to delete.
* @return {module:akkaserverless.replicatedentity.ReplicatedCounterMap} This counter map.
*/
this.delete = function (key) {
const comparableKey = AnySupport.toComparable(key);
if (counters.has(comparableKey)) {
counters.delete(comparableKey);
removed.set(comparableKey, key);
}
return this;
};
/**
* Clear all counters from this counter map.
*
* @function module:akkaserverless.replicatedentity.ReplicatedCounterMap#clear
* @return {module:akkaserverless.replicatedentity.ReplicatedCounterMap} This counter map.
*/
this.clear = function () {
if (counters.size > 0) {
cleared = true;
counters.clear();
removed.clear();
}
return this;
};
this.getOrCreateCounter = function (key) {
const comparableKey = AnySupport.toComparable(key);
const entry = counters.get(comparableKey);
if (entry) {
return entry.counter;
} else {
const counter = new ReplicatedCounter();
counters.set(comparableKey, { key: key, counter: counter });
return counter;
}
};
this.getAndResetDelta = function (initial) {
const updated = [];
counters.forEach(({ key: key, counter: counter }, comparableKey) => {
const delta = counter.getAndResetDelta();
if (delta !== null) {
updated.push({
key: AnySupport.serialize(key, true, true),
delta: delta.counter,
});
}
});
if (cleared || removed.size > 0 || updated.length > 0 || initial) {
const delta = {
replicatedCounterMap: {
cleared: cleared,
removed: Array.from(removed.values()).map((key) =>
AnySupport.serialize(key, true, true),
),
updated: updated,
},
};
cleared = false;
removed.clear();
return delta;
} else {
return null;
}
};
this.applyDelta = function (delta, anySupport) {
if (!delta.replicatedCounterMap) {
throw new Error(
util.format('Cannot apply delta %o to ReplicatedCounterMap', delta),
);
}
if (delta.replicatedCounterMap.cleared) {
counters.clear();
}
if (delta.replicatedCounterMap.removed) {
delta.replicatedCounterMap.removed.forEach((serializedKey) => {
const key = anySupport.deserialize(serializedKey);
const comparableKey = AnySupport.toComparable(key);
if (counters.has(comparableKey)) {
counters.delete(comparableKey);
} else {
debug('Key to delete [%o] is not in ReplicatedCounterMap', key);
}
});
}
if (delta.replicatedCounterMap.updated) {
delta.replicatedCounterMap.updated.forEach((entry) => {
const key = anySupport.deserialize(entry.key);
this.getOrCreateCounter(key).applyDelta({ counter: entry.delta });
});
}
};
this.toString = function () {
return (
'ReplicatedCounterMap(' +
Array.from(counters.values())
.map((entry) => entry.key + ' -> ' + entry.counter.value)
.join(', ') +
')'
);
};
}
module.exports = ReplicatedCounterMap;