UNPKG

@lightbend/akkaserverless-javascript-sdk

Version:
237 lines (221 loc) 7.29 kB
/* * 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 util = require('util'); const AnySupport = require('../protobuf-any'); /** * @classdesc A Replicated Set data type. * * A ReplicatedSet is a set of {@link module:akkaserverless.Serializable} values. Elements can be added and removed. * * @constructor module:akkaserverless.replicatedentity.ReplicatedSet * @implements module:akkaserverless.replicatedentity.ReplicatedData * @implements Iterable<module:akkaserverless.Serializable> */ function ReplicatedSet() { // Map of a comparable form (that compares correctly using ===) of the elements to the elements let currentValue = new Map(); let delta = { added: new Map(), removed: new Map(), cleared: false, }; /** * Does this set contain the given element? * * @function module:akkaserverless.replicatedentity.ReplicatedSet#has * @param {module:akkaserverless.Serializable} element The element to check. * @returns {boolean} True if the set contains the element. */ this.has = function (element) { return currentValue.has(AnySupport.toComparable(element)); }; /** * The number of elements in this set. * * @name module:akkaserverless.replicatedentity.ReplicatedSet#size * @type {number} * @readonly */ Object.defineProperty(this, 'size', { get: function () { return currentValue.size; }, }); /** * Execute the given callback for each element. * * @function module:akkaserverless.replicatedentity.ReplicatedSet#forEach * @param {module:akkaserverless.replicatedentity.ReplicatedSet~forEachCallback} callback The callback to handle each element. */ this.forEach = function (callback) { return currentValue.forEach((value, key) => callback(value)); }; /** * Create an iterator for this set. * * @function module:akkaserverless.replicatedentity.ReplicatedSet#iterator * @returns {Iterator<module:akkaserverless.Serializable>} */ this[Symbol.iterator] = function () { return currentValue.values(); }; /** * Get a copy of the current elements as a Set. * * @function module:akkaserverless.replicatedentity.ReplicatedSet#elements * @return {Set<module:akkaserverless.Serializable>} */ this.elements = function () { return new Set(currentValue.values()); }; /** * Add an element to this set. * * @function module:akkaserverless.replicatedentity.ReplicatedSet#add * @param {module:akkaserverless.Serializable} element The element to add. * @return {module:akkaserverless.replicatedentity.ReplicatedSet} This set. */ this.add = function (element) { const comparable = AnySupport.toComparable(element); if (!currentValue.has(comparable)) { if (delta.removed.has(comparable)) { delta.removed.delete(comparable); } else { const serializedElement = AnySupport.serialize(element, true, true); delta.added.set(comparable, serializedElement); } currentValue.set(comparable, element); } return this; }; /** * Add multiple elements to this set. * * @function module:akkaserverless.replicatedentity.ReplicatedSet#addAll * @param {Iterator<module:akkaserverless.Serializable>} elements The elements to add. * @return {module:akkaserverless.replicatedentity.ReplicatedSet} This set. */ this.addAll = function (elements) { for (const element of elements) { this.add(element); } return this; }; /** * Remove an element from this set. * * @function module:akkaserverless.replicatedentity.ReplicatedSet#delete * @param {module:akkaserverless.Serializable} element The element to delete. * @return {module:akkaserverless.replicatedentity.ReplicatedSet} This set. */ this.delete = function (element) { const comparable = AnySupport.toComparable(element); if (currentValue.has(comparable)) { if (currentValue.size === 1) { this.clear(); } else { currentValue.delete(comparable); if (delta.added.has(comparable)) { delta.added.delete(comparable); } else { const serializedElement = AnySupport.serialize(element, true, true); delta.removed.set(comparable, serializedElement); } } } return this; }; /** * Remove all elements from this set. * * @function module:akkaserverless.replicatedentity.ReplicatedSet#clear * @return {module:akkaserverless.replicatedentity.ReplicatedSet} This set. */ this.clear = function () { if (currentValue.size > 0) { delta.cleared = true; delta.added.clear(); delta.removed.clear(); currentValue.clear(); } return this; }; this.getAndResetDelta = function (initial) { if ( delta.cleared || delta.added.size > 0 || delta.removed.size > 0 || initial ) { const currentDelta = { replicatedSet: { cleared: delta.cleared, removed: Array.from(delta.removed.values()), added: Array.from(delta.added.values()), }, }; delta.cleared = false; delta.added.clear(); delta.removed.clear(); return currentDelta; } else { return null; } }; this.applyDelta = function (delta, anySupport) { if (!delta.replicatedSet) { throw new Error( util.format('Cannot apply delta %o to ReplicatedSet', delta), ); } if (delta.replicatedSet.cleared) { currentValue.clear(); } if (delta.replicatedSet.removed !== undefined) { delta.replicatedSet.removed.forEach((element) => { const value = anySupport.deserialize(element); const comparable = AnySupport.toComparable(value); if (currentValue.has(comparable)) { currentValue.delete(comparable); } else { debug( "Delta instructed to delete element [%o], but it wasn't in the ReplicatedSet.", comparable, ); } }); } if (delta.replicatedSet.added !== undefined) { delta.replicatedSet.added.forEach((element) => { const value = anySupport.deserialize(element); const comparable = AnySupport.toComparable(value); if (currentValue.has(comparable)) { debug( "Delta instructed to add value [%o], but it's already present in the ReplicatedSet", comparable, ); } else { currentValue.set(comparable, value); } }); } }; this.toString = function () { return 'ReplicatedSet(' + Array.from(currentValue).join(',') + ')'; }; } module.exports = ReplicatedSet;