Files
discordeno/packages/utils/src/Collection.ts
2023-01-05 13:47:49 -06:00

168 lines
5.0 KiB
TypeScript

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PlaceHolderBot {}
export class Collection<K, V> extends Map<K, V> {
/**
* The maximum amount of items allowed in this collection. To disable cache, set it 0, set to undefined to make it infinite.
* @default undefined
*/
maxSize: number | undefined
/** Handler to remove items from the collection every so often. */
sweeper: (CollectionSweeper<K, V> & { intervalId?: NodeJS.Timer }) | undefined
constructor(entries?: (ReadonlyArray<readonly [K, V]> | null) | Map<K, V>, options?: CollectionOptions<K, V>) {
super(entries ?? [])
this.maxSize = options?.maxSize
if (!options?.sweeper) return
this.startSweeper(options.sweeper)
}
startSweeper(options: CollectionSweeper<K, V>): NodeJS.Timer {
if (this.sweeper?.intervalId) clearInterval(this.sweeper.intervalId)
this.sweeper = options
this.sweeper.intervalId = setInterval(() => {
this.forEach((value, key) => {
if (!this.sweeper?.filter(value, key, options.bot)) return
this.delete(key)
return key
})
}, options.interval)
return this.sweeper.intervalId
}
stopSweeper(): void {
return clearInterval(this.sweeper?.intervalId)
}
changeSweeperInterval(newInterval: number): void {
if (this.sweeper == null) return
this.startSweeper({ filter: this.sweeper.filter, interval: newInterval })
}
changeSweeperFilter(newFilter: (value: V, key: K, bot: PlaceHolderBot) => boolean): void {
if (this.sweeper == null) return
this.startSweeper({ filter: newFilter, interval: this.sweeper.interval })
}
/** Add an item to the collection. Makes sure not to go above the maxSize. */
set(key: K, value: V): this {
// When this collection is maxSized make sure we can add first
if ((this.maxSize !== undefined || this.maxSize === 0) && this.size >= this.maxSize) {
return this
}
return super.set(key, value)
}
/** Add an item to the collection, no matter what the maxSize is. */
forceSet(key: K, value: V): this {
return super.set(key, value)
}
/** Convert the collection to an array. */
array(): V[] {
return [...this.values()]
}
/** Retrieve the value of the first element in this collection. */
first(): V | undefined {
return this.values().next().value
}
/** Retrieve the value of the last element in this collection. */
last(): V | undefined {
return [...this.values()][this.size - 1]
}
/** Retrieve the value of a random element in this collection. */
random(): V | undefined {
const array = [...this.values()]
return array[Math.floor(Math.random() * array.length)]
}
/** Find a specific element in this collection. */
find(callback: (value: V, key: K) => boolean): NonNullable<V> | undefined {
for (const key of this.keys()) {
const value = this.get(key)!
if (callback(value, key)) return value
}
// If nothing matched
}
/** Find all elements in this collection that match the given pattern. */
filter(callback: (value: V, key: K) => boolean): Collection<K, V> {
const relevant = new Collection<K, V>()
this.forEach((value, key) => {
if (callback(value, key)) relevant.set(key, value)
})
return relevant
}
/** Converts the collection into an array by running a callback on all items in the collection. */
map<T>(callback: (value: V, key: K) => T): T[] {
const results = []
for (const key of this.keys()) {
const value = this.get(key)!
results.push(callback(value, key))
}
return results
}
/** Check if one of the items in the collection matches the pattern. */
some(callback: (value: V, key: K) => boolean): boolean {
for (const key of this.keys()) {
const value = this.get(key)!
if (callback(value, key)) return true
}
return false
}
/** Check if all of the items in the collection matches the pattern. */
every(callback: (value: V, key: K) => boolean): boolean {
for (const key of this.keys()) {
const value = this.get(key)!
if (!callback(value, key)) return false
}
return true
}
/** Runs a callback on all items in the collection, merging them into a single value. */
reduce<T>(callback: (accumulator: T, value: V, key: K) => T, initialValue?: T): T {
let accumulator: T = initialValue!
for (const key of this.keys()) {
const value = this.get(key)!
accumulator = callback(accumulator, value, key)
}
return accumulator
}
}
export interface CollectionOptions<K, V> {
/** Handler to clean out the items in the collection every so often. */
sweeper?: CollectionSweeper<K, V>
/** The maximum number of items allowed in the collection. */
maxSize?: number
}
export interface CollectionSweeper<K, V> {
/** The filter to determine whether an element should be deleted or not */
filter: (value: V, key: K, ...args: any[]) => boolean
/** The interval in which the sweeper should run */
interval: number
/** The bot object itself */
bot?: PlaceHolderBot
}