203 lines
5.9 KiB
JavaScript
Raw Permalink Normal View History

2023-11-29 18:23:54 +03:00
"use strict";
/**
* Hold a bunch of something
* @extends Map
* @prop {Class} baseObject The base class for all items
* @prop {Number?} limit Max number of items to hold
*/
class Collection extends Map {
/**
* Construct a Collection
* @arg {Class} baseObject The base class for all items
* @arg {Number} [limit] Max number of items to hold
*/
constructor(baseObject, limit) {
super();
this.baseObject = baseObject;
this.limit = limit;
}
/**
* Update an object
* @arg {Object} obj The updated object data
* @arg {String} obj.id The ID of the object
* @arg {Class} [extra] An extra parameter the constructor may need
* @arg {Boolean} [replace] Whether to replace an existing object with the same ID
* @returns {Class} The updated object
*/
update(obj, extra, replace) {
if(!obj.id && obj.id !== 0) {
throw new Error("Missing object id");
}
const item = this.get(obj.id);
if(!item) {
return this.add(obj, extra, replace);
}
item.update(obj, extra);
return item;
}
/**
* Add an object
* @arg {Object} obj The object data
* @arg {String} obj.id The ID of the object
* @arg {Class} [extra] An extra parameter the constructor may need
* @arg {Boolean} [replace] Whether to replace an existing object with the same ID
* @returns {Class} The existing or newly created object
*/
add(obj, extra, replace) {
if(this.limit === 0) {
return (obj instanceof this.baseObject || obj.constructor.name === this.baseObject.name) ? obj : new this.baseObject(obj, extra);
}
if(obj.id == null) {
throw new Error("Missing object id");
}
const existing = this.get(obj.id);
if(existing && !replace) {
return existing;
}
if(!(obj instanceof this.baseObject || obj.constructor.name === this.baseObject.name)) {
obj = new this.baseObject(obj, extra);
}
this.set(obj.id, obj);
if(this.limit && this.size > this.limit) {
const iter = this.keys();
while(this.size > this.limit) {
this.delete(iter.next().value);
}
}
return obj;
}
/**
* Returns true if all elements satisfy the condition
* @arg {Function} func A function that takes an object and returns true or false
* @returns {Boolean} Whether or not all elements satisfied the condition
*/
every(func) {
for(const item of this.values()) {
if(!func(item)) {
return false;
}
}
return true;
}
/**
* Return all the objects that make the function evaluate true
* @arg {Function} func A function that takes an object and returns true if it matches
* @returns {Array<Class>} An array containing all the objects that matched
*/
filter(func) {
const arr = [];
for(const item of this.values()) {
if(func(item)) {
arr.push(item);
}
}
return arr;
}
/**
* Return the first object to make the function evaluate true
* @arg {Function} func A function that takes an object and returns true if it matches
* @returns {Class?} The first matching object, or undefined if no match
*/
find(func) {
for(const item of this.values()) {
if(func(item)) {
return item;
}
}
return undefined;
}
/**
* Return an array with the results of applying the given function to each element
* @arg {Function} func A function that takes an object and returns something
* @returns {Array} An array containing the results
*/
map(func) {
const arr = [];
for(const item of this.values()) {
arr.push(func(item));
}
return arr;
}
/**
* Get a random object from the Collection
* @returns {Class?} The random object, or undefined if there is no match
*/
random() {
const index = Math.floor(Math.random() * this.size);
const iter = this.values();
for(let i = 0; i < index; ++i) {
iter.next();
}
return iter.next().value;
}
/**
* Returns a value resulting from applying a function to every element of the collection
* @arg {Function} func A function that takes the previous value and the next item and returns a new value
* @arg {any} [initialValue] The initial value passed to the function
* @returns {any} The final result
*/
reduce(func, initialValue) {
const iter = this.values();
let val;
let result = initialValue === undefined ? iter.next().value : initialValue;
while((val = iter.next().value) !== undefined) {
result = func(result, val);
}
return result;
}
/**
* Remove an object
* @arg {Object} obj The object
* @arg {String} obj.id The ID of the object
* @returns {Class?} The removed object, or null if nothing was removed
*/
remove(obj) {
const item = this.get(obj.id);
if(!item) {
return null;
}
this.delete(obj.id);
return item;
}
/**
* Returns true if at least one element satisfies the condition
* @arg {Function} func A function that takes an object and returns true or false
* @returns {Boolean} Whether or not at least one element satisfied the condition
*/
some(func) {
for(const item of this.values()) {
if(func(item)) {
return true;
}
}
return false;
}
toString() {
return `[Collection<${this.baseObject.name}>]`;
}
toJSON() {
const json = {};
for(const item of this.values()) {
json[item.id] = item;
}
return json;
}
}
module.exports = Collection;