seyfert/src/cache/adapters/limited.ts

304 lines
7.9 KiB
TypeScript

import { LimitedCollection } from '../..';
import { type MakeRequired, MergeOptions } from '../../common';
import type { Adapter } from './types';
//TODO: optimizar esto
export interface ResourceLimitedMemoryAdapter {
expire?: number;
limit?: number;
}
export interface LimitedMemoryAdapterOptions<T> {
default?: ResourceLimitedMemoryAdapter;
guild?: ResourceLimitedMemoryAdapter;
user?: ResourceLimitedMemoryAdapter;
ban?: ResourceLimitedMemoryAdapter;
member?: ResourceLimitedMemoryAdapter;
voice_state?: ResourceLimitedMemoryAdapter;
channel?: ResourceLimitedMemoryAdapter;
emoji?: ResourceLimitedMemoryAdapter;
presence?: ResourceLimitedMemoryAdapter;
role?: ResourceLimitedMemoryAdapter;
stage_instance?: ResourceLimitedMemoryAdapter;
sticker?: ResourceLimitedMemoryAdapter;
thread?: ResourceLimitedMemoryAdapter;
overwrite?: ResourceLimitedMemoryAdapter;
message?: ResourceLimitedMemoryAdapter;
encode?(data: any): T;
decode?(data: T): unknown;
}
export class LimitedMemoryAdapter<T> implements Adapter {
isAsync = false;
readonly storage = new Map<string, LimitedCollection<string, T>>();
readonly relationships = new Map<string, Map<string, string[]>>();
options: MakeRequired<LimitedMemoryAdapterOptions<T>, 'default' | 'encode' | 'decode'>;
constructor(options?: LimitedMemoryAdapterOptions<T>) {
this.options = MergeOptions(
{
default: {
expire: undefined,
limit: Number.POSITIVE_INFINITY,
},
encode(data) {
return data;
},
decode(data) {
return data;
},
} satisfies LimitedMemoryAdapterOptions<T>,
options,
);
}
start() {
//
}
scan(query: string, keys?: false): any[];
scan(query: string, keys: true): string[];
scan(query: string, keys = false) {
const values: (string | unknown)[] = [];
const sq = query.split('.');
for (const iterator of [...this.storage.values()].flatMap(x => x.entries()))
for (const [key, value] of iterator) {
if (key.split('.').every((value, i) => (sq[i] === '*' ? !!value : sq[i] === value))) {
values.push(keys ? key : this.options.decode(value.value));
}
}
return values;
}
bulkGet(keys: string[]) {
const iterator = [...this.storage.values()];
return keys
.map(key => {
const data = iterator.find(x => x.has(key))?.get(key);
return data ? this.options.decode(data) : null;
})
.filter(x => x);
}
get(keys: string) {
const data = [...this.storage.values()].find(x => x.has(keys))?.get(keys);
return data ? this.options.decode(data) : null;
}
private __set(key: string, data: any) {
const __guildId = Array.isArray(data) ? data[0].guild_id : data.guild_id;
const namespace = `${key.split('.')[0]}${__guildId ? `.${__guildId}` : ''}`;
const self = this;
if (!this.storage.has(namespace)) {
this.storage.set(
namespace,
new LimitedCollection({
expire:
this.options[key.split('.')[0] as Exclude<keyof LimitedMemoryAdapterOptions<T>, 'decode' | 'encode'>]
?.expire ?? this.options.default.expire,
limit:
this.options[key.split('.')[0] as Exclude<keyof LimitedMemoryAdapterOptions<T>, 'decode' | 'encode'>]
?.limit ?? this.options.default.limit,
resetOnDemand: true,
onDelete(k) {
const relationshipNamespace = key.split('.')[0];
const existsRelation = self.relationships.has(relationshipNamespace);
if (existsRelation) {
switch (relationshipNamespace) {
case 'ban':
case 'member':
case 'voice_state':
{
const split = k.split('.');
self.removeToRelationship(`${namespace}.${split[1]}`, split[2]);
}
break;
case 'channel':
case 'emoji':
case 'presence':
case 'role':
case 'stage_instance':
case 'sticker':
case 'thread':
case 'overwrite':
case 'message':
self.removeToRelationship(namespace, k.split('.')[1]);
break;
// case 'guild':
// case 'user':
default:
self.removeToRelationship(namespace, k.split('.')[1]);
break;
}
}
},
}),
);
}
this.storage.get(namespace)!.set(key, this.options.encode(data));
}
bulkSet(keys: [string, any][]) {
for (const [key, value] of keys) {
this.__set(key, value);
}
}
set(keys: string, data: any) {
this.__set(keys, data);
}
bulkPatch(updateOnly: boolean, keys: [string, any][]) {
for (const [key, value] of keys) {
const oldData = this.get(key);
if (updateOnly && !oldData) {
continue;
}
this.__set(key, Array.isArray(value) ? value : { ...(oldData ?? {}), ...value });
}
}
patch(updateOnly: boolean, keys: string, data: any) {
const oldData = this.get(keys);
if (updateOnly && !oldData) {
return;
}
this.__set(keys, Array.isArray(data) ? data : { ...(oldData ?? {}), ...data });
}
values(to: string) {
const array: any[] = [];
const data = this.keys(to);
for (const key of data) {
const content = this.get(key);
if (content) {
array.push(content);
}
}
return array;
}
keys(to: string) {
return this.getToRelationship(to).map(id => `${to}.${id}`);
}
count(to: string) {
return this.getToRelationship(to).length;
}
bulkRemove(keys: string[]) {
for (const i of keys) {
this.remove(i);
}
}
remove(key: string) {
const keySplit = key.split('.');
const parentNamespace = keySplit.at(0)!;
switch (parentNamespace) {
case 'ban':
case 'member':
case 'voice_state':
{
this.storage
.get(`${parentNamespace}.${keySplit.at(1)}`)
?.delete(`${parentNamespace}.${keySplit.at(1)}.${keySplit.at(2)}`);
}
break;
case 'channel':
case 'emoji':
case 'presence':
case 'role':
case 'stage_instance':
case 'sticker':
case 'thread':
case 'overwrite':
case 'message':
for (const keyStorage of this.storage.keys()) {
if (keyStorage.startsWith(parentNamespace)) {
const storage = this.storage.get(keyStorage)!;
if (storage.has(key)) {
storage.delete(key);
break;
}
}
}
break;
// case 'user':
// case 'guild':
default:
this.storage.get(parentNamespace)?.delete(`${parentNamespace}.${keySplit.at(1)}`);
break;
}
}
flush(): void {
this.storage.clear();
this.relationships.clear();
}
contains(to: string, keys: string): boolean {
return this.getToRelationship(to).includes(keys);
}
getToRelationship(to: string) {
const key = to.split('.')[0];
if (!this.relationships.has(key)) this.relationships.set(key, new Map<string, string[]>());
const relation = this.relationships.get(key)!;
const subrelationKey = to.split('.')[1] ?? '*';
if (!relation.has(subrelationKey)) {
relation.set(subrelationKey, []);
}
return relation.get(subrelationKey)!;
}
bulkAddToRelationShip(data: Record<string, string[]>) {
for (const i in data) {
this.addToRelationship(i, data[i]);
}
}
addToRelationship(to: string, keys: string | string[]) {
const key = to.split('.')[0];
if (!this.relationships.has(key)) {
this.relationships.set(key, new Map<string, string[]>());
}
const data = this.getToRelationship(to);
for (const key of Array.isArray(keys) ? keys : [keys]) {
if (!data.includes(key)) {
data.push(key);
}
}
}
removeToRelationship(to: string, keys: string | string[]) {
const data = this.getToRelationship(to);
if (data) {
for (const key of Array.isArray(keys) ? keys : [keys]) {
const idx = data.indexOf(key);
if (idx !== -1) {
data.splice(idx, 1);
}
}
}
}
removeRelationship(to: string | string[]) {
for (const i of Array.isArray(to) ? to : [to]) {
this.relationships.delete(i);
}
}
}